首先要引redux.js包,这个包提供了Redux对象,这个对象可以调用Redux.createStore()方法。
<body>
<h1id="info"></h1>
<buttonid="btn1">加</button>
<buttonid="btn2">减</button>
<buttonid="btn3">乘</button>
<buttonid="btn4">除</button>
<scripttype="text/javascript"src="redux.min.js"></script>
<scripttype="text/javascript">
//这是一个reducer,形式为(state,action)=>state的纯函数。
//纯函数:函数内部,不改变传入的参数,只return新值。
//描述了action如何把state转变成下一个state。
constreducer=(state={"v":10},action)=>{
if(action.type=="ADD"){
return{"v":state.v+1};
}elseif(action.type=="MINUS"){
return{"v":state.v-1};
}elseif(action.type=="CHENG2"){
return{"v":state.v*2}
}
returnstate;
}
//创建Reduxstore来存放应用的状态。
//store翻译为“仓库”,这是一个存放数据并且可以操作数据的东西
conststore=Redux.createStore(reducer);
//创建一个视图函数,并且将store显示到视图上。
constrender=()=>{
//store的getState()方法可以得到仓库中的数据
document.getElementById("info").innerHTML=store.getState().v;
}
render();//调用render函数
//要将store注册到视图,这样的话,当store中的数据变化候,就能自动调用render函数
store.subscribe(render);
//应用中所有的state都以一个对象树的形式储存在一个单一的store中。
//惟一改变state的办法是触发action,一个描述发生什么的对象。
//为了描述action如何改变state树,你需要编写reducers。
document.getElementById("btn1").onclick=function(){
//改变内部state惟一方法是dispatch一个action,type表示要做的动作
store.dispatch({"type":"ADD"})
}
document.getElementById("btn2").onclick=function(){
store.dispatch({"type":"MINUS"})
}
document.getElementById("btn3").onclick=function(){
store.dispatch({"type":"CHENG"})
}
</script>
</body>
创建一个叫做reducer的函数,reducer中“维持”一个量,叫state,初始值是{"v":0}。
这个函数你必须知道两点:
1)它是一个纯函数,在函数内部不改变{"v":0}的,不改变state对象的,只是返回了新的state。
constreducer=(state={"v":0},action)=>{
if(action.type=="ADD"){
return{"v":state.v+1};
}elseif(action.type=="MINUS"){
return{"v":state.v-1};
}
returnstate;
}
2)这个函数提供了可被预测的功能。
reducer中的state,就像被关进了保险箱。任何对这个state的变化,只能是通过dispatch一个action来改变的。换句话说,只有dispatch一个action才能改变这个保险箱中的数据。
在reducer函数中,用if语句来表示对state可能发生变化的罗列,只有罗列在楼层里面的改变,才会发生:
if(action.type==""){
return新的state
}elseif(action.type==""){
return新的state
}elseif(action.type==""){
return新的state
}
示例代码
创建store仓库,这个仓库创建时需要提供reducer,所以可以认为store就是reducer,reducer就是store。
conststore=Redux.createStore(reducer);
reducer是一个纯函数,而store提供了三个方法:
store.subscribe()注册到视图
store.getState()得到数据
store.dispatch()发送action
学习redux和react结合,到时候就不用注册到视图。
然后创建监听:
document.getElementById("btn1").onclick=function(){
store.dispatch({"type":"ADD"})
}
document.getElementById("btn2").onclick=function(){
store.dispatch({"type":"MINUS"})
}
document.getElementById("btn3").onclick=function(){
store.dispatch({"type":"CHENG"})
}
示例代码
点击按钮,store要dispatch出一个action。所谓的action就是一个JSON,这个JSON必须有type属性,值为大写字母。这个action没有任何意义,比如{"type":"ADD"},但reducer认识这个action,可以产生变化!
案例2,添加载荷:
<body>
<h1id="info"></h1>
<buttonid="btn1">加</button>
<inputtype="text"id="txt">
<buttonid="btn2">加输入的</button>
<scripttype="text/javascript"src="redux.min.js"></script>
<scripttype="text/javascript">
constreducer=(state={"v":10},{type,data})=>{
if(type=="ADD"){
return{"v":state.v+action.data}
return{"v":state.v+data}
}
returnstate;
}
conststore=Redux.createStore(reducer);
constrender=()=>{
document.getElementById("info").innerHTML=store.getState().v;
}
render();
store.subscribe(render);
document.getElementById("btn1").onclick=function(){
store.dispatch({type:'ADD',data:100});
}
document.getElementById("btn2").onclick=function(){
vara=parseInt(document.getElementById("txt").value);
//载荷payload。
store.dispatch({"type":"ADD",data:a});
}
</script>
</body>
唯一可以改变state的方式dispatch一个action
案例3:添加数组
<body>
<divid="box">
<p><inputtype="text"id="name"></p>
<p><inputtype="text"id="age"></p>
<buttonid="btn">增加</button>
<ulid="List">
</ul>
</div>
<scripttype="text/javascript"src="redux.min.js"></script>
<scripttype="text/javascript">
//初始数据
constinitObj={
"arr":[
{"name":"小明","age":12},
{"name":"小红","age":13},
{"name":"小强","age":14}
]
};
//reducer函数
constreducer=(state=initObj,{type,name,age})=>{
if(type=="ADD_STUDENT"){
//不能push改变传入的原数组,所以要返回新数组
return{
"arr":[
{name,age},
...state.arr
]
}
}
returnstate;
}
//创建store
conststore=Redux.createStore(reducer);
//视图函数
constrender=function(){
//清空ul
document.getElementById("List").innerHTML="";
//创建li
for(vari=0;i<store.getState().arr.length;i++){
varli=document.createElement("li");
varstoreArr=store.getState().arr[i]
li.innerHTML=storeArr.name+storeArr.age+"岁"
document.getElementById("List").appendChild(li);
}
}
render();//运行视图函数
store.subscribe(render);//注册到视图
document.getElementById("btn").onclick=function(){
varname=document.getElementById("name").value;
varage=document.getElementById("age").value;
//发出Action,这是唯一能够改变store的途径
store.dispatch({"type":"ADD_STUDENT",name,age})
}
</script>
</body>
案例4,加深练习:
<body>
<h1id="info"></h1>
<h1id="info2"></h1>
<buttonid="btn">+</button>
<scripttype="text/javascript"src="redux.min.js"></script>
<scripttype="text/javascript">
varinitObj={
"a":{
"b":{
"c":{
"v":10
}
},
"m":8
}
}
constreducer=(state=initObj,action)=>{
if(action.type=="ADD"){
return{
...state,
"a":{
...state.a,
"b":{
...state.a.b,
"c":{
...state.a.b.c,
"v":state.a.b.c.v+1
}
}
}
}
}
returnstate;
}
conststore=Redux.createStore(reducer);
constrender=()=>{
document.getElementById("info").innerHTML=store.getState().a.b.c.v;
document.getElementById("info2").innerHTML=store.getState().a.m;
}
render();
store.subscribe(render);
document.getElementById("btn").onclick=function(){
store.dispatch({"type":"ADD"});
}
</script>
</body>
二、Redux和React进行结合开发
在React开发的时候使用Redux可预测状态容器,要装两个新的依赖:
redux:提供createStore、combineReducers、bindActionCreators等功能
react-redux:只提供两个东西,<Provider>组件、connect()函数。
安装两个依赖:
npminstall--savereduxreact-redux
创建reducers文件夹,创建index.js,这个文件暴露一个纯函数:
reducers/index.js:
exportdefault(state={v:10},action)=>{
returnstate;
}
main.js入口文件:
importReactfrom"react";
importReactDOMfrom"react-dom";
import{createStore}from"redux";
importAppfrom"./App.js";
importreducerfrom"./reducers";//reducer函数
//创建Reduxstore来存放应用的状态。
conststore=createStore(reducer);
ReactDOM.render(
<App></App>,
document.getElementById("app")
);
有两个问题:
如何让组件能够访问到store中的数据?
如果让store中的数据改变的时候,能够自动更新组件的视图
react-redux解决了这两个问题。
在main.js中创建<Provider>组件,它提供的是一个顶层容器的作用,实现store的上下文传递,是让store能够“注入”到所有组件
react-redux提供Provider组件,可以让容器组件拿到state
Provider在根组件外面包一层,这样一来,App所有的子组件就默认都拿到了state了。
原理是React组件的context属性,就是将store这个对象放到上下文(context)中
importReactfrom"react";
importReactDOMfrom"react-dom";
import{createStore}from"redux";
import{Provider}from"react-redux";
importAppfrom"./App.js";
importreducerfrom"./reducers";
//创建store仓库
conststore=createStore(reducer);
ReactDOM.render(
<Providerstore={store}>
<App></App>
</Provider>,
document.getElementById('app')
);
组件中要获得全局store的值,需要用connect函数,connect提供连接React组件与Reduxstore的作用。
connect([mapStateToProps],[mapDispatchToProps])
connect()的第一个参数:mapStateToProps这个函数的第一个参数就是Redux的store,会自动将store的数据作为props绑定到组件上。
connect()的第二个参数:mapDispatchToProps它的功能是,将action作为props绑定到组件上
通俗理解,使用connect可以把state和dispatch绑定到React组件,使得组件可以访问到redux的数据。
常看到下面这种写法:
exportdefaultconnect()(App)
App.js
importReactfrom"react";
import{connect}from"react-redux";
classAppextendsReact.Component{
constructor(){
super();
}
render(){
return<div>
<h1>{this.props.v}</h1>
</div>
}
}
exportdefaultconnect(
//这个函数return的对象的值,将自动成为组件的props。
(state)=>{
return{
v:state.v
}
}
)(App);
一旦connect()(App);连接某个组件,此时这个组件就会:当全局store发送改变时,如同自己的props发生改变一样,从而进行视图更新。
在App.js写两个按钮,可以加1,减1:
importReactfrom"react";
import{connect}from"react-redux";
classAppextendsReact.Component{
constructor(){
super();
}
render(){
return<div>
<h1>{this.props.v}</h1>
<buttononClick={()=>{this.props.add()}}>+</button>
<buttononClick={()=>{this.props.minus()}}>-</button>
</div>
}
}
exportdefaultconnect(
(state)=>{
return{
v:state.v
}
},
(dispatch)=>{
return{
add(){
dispatch({"type":"ADD"});
},
minus(){
dispatch({"type":"MINUS"});
}
}
}
)(App);
reducers/index.js提供可预测状态
exportdefault(state={"v":10},action)=>{
if(action.type=="ADD"){
return{"v":state.v+1};
}elseif(action.type=="MINUS"){
return{"v":state.v-1};
}
returnstate;
}
学生管理系统小案例:
reducers/index.js
constinitObj={
"arr":[
{"id":1,"name":"小明","age":12},
{"id":2,"name":"小红","age":13},
{"id":3,"name":"小刚","age":14}
]
}
exportdefault(state=initObj,{type,age,name,id})=>{
if(type=="ADD_STUDENT"){
return{
...state,
"arr":[
...state.arr,
{
"id":state.arr.reduce((a,b)=>{
returnb.id>a?b.id:a;
},0)+1,
name,
age
}
]
}
}elseif(type=="DEL_STUDENT"){
return{
...state,
"arr":state.arr.filter(item=>item.id!=id)
}
}
returnstate;
}
App.js
importReactfrom"react";
import{connect}from"react-redux";
classAppextendsReact.Component{
constructor(){
super();
}
//增加学生
add(){
varname=this.refs.name.value;
varage=this.refs.age.value;
this.props.addStudent(name,age)
}
render(){
return<div>
<p><inputtype="text"ref="name"/></p>
<p><inputtype="text"ref="age"/></p>
<buttononClick={()=>{this.add()}}>增加学生</button>
{
this.props.arr.map((item,index)=>{
return<pkey={item.id}>
{item.id}{item.name}{item.age}岁
<buttononClick={()=>{this.props.delStudent(item.id)}}>删除</button>
</p>
})
}
</div>
}
}
exportdefaultconnect(
//(state)=>{
//return{arr:state.arr}
//},
//简化写法
({arr})=>{
return{arr}
},
(dispatch)=>{
return{
addStudent(name,age){
dispatch({"type":"ADD_STUDENT",name,age})
},
delStudent(id){
dispatch({"type":"DEL_STUDENT",id})
}
}
}
)(App);
原理解析
首先connect之所以会成功,是因为Provider组件:
在原应用组件上包裹一层,使原来整个应用成为Provider的子组件
接收Redux的store作为props,通过context对象传递给子孙组件上的connect
connect做了些什么?
它真正连接Redux和React,它包在我们的容器组件的外一层,它接收Provider提供的store里面的state和dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。
总结:
connect()(App),第一个()中接受两个参数,分别是:mapStateToProps、mapDispatchToProps。
这两个参数都是函数,第一个参数函数return的对象的键名将自动和props进行绑定,第二个参数函数return的对象的键名,也将和props进行绑定。
第一个参数return的对象,是从state中获得值
第二个参数return的对象,是要改变state的值
如果有兴趣,可以看一下connect函数的API文档:
https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
不管应用程序有多大,store只有一个,它就像天神一样“照耀”所有组件,但默认情况下所有组件是不能得到store的数据的,哪个组件要拿数据,就要connect一下,另外App最大组件确实包裹着所有组件,但不代表App组件连接了就代表其他组件也连接了。
三、Redux编程-TodoList
http://www.todolist.cn/
在reducers/index.js中根据项目情况创建reducer:
constinitObj={
"todos":[
{"id":1,"title":"吃饭","done":false},
{"id":2,"title":"睡觉","done":false},
{"id":3,"title":"打豆豆","done":false}
]
}
exportdefault(state=initObj,action)=>{
returnstate;
}
分别创建TodoHd.js、TodoBd.js、TodoFt.js三个组件:
importReactfrom"react";
exportdefaultclassTodoHdextendsReact.Component{
constructor(){
super();
}
render(){
return<div>
<h1>我是TodoHd组件</h1>
</div>
}
}
示例代码
App.js引入组件
importReactfrom"react";
import{connect}from"react-redux";
importTodoHdfrom"./components/TodoHd.js";
importTodoBdfrom"./components/TodoBd.js";
importTodoFtfrom"./components/TodoFt.js";
classAppextendsReact.Component{
constructor(){
super();
}
render(){
return<div>
<TodoHd></TodoHd>
<TodoBd></TodoBd>
<TodoFt></TodoFt>
</div>
}
}
exportdefaultconnect()(App)
importReactfrom'react';
import{connect}from"react-redux";
importTodoItemfrom"./TodoItem.js";
classTodoBdextendsReact.Component{
constructor(props){
super(props);
}
render(){
return(
<div>
//{JSON.stringify(this.props.todos)}
{
this.props.todos.map(item=>{
//return<divkey={item.id}>{item.title}</div>
return<TodoItemkey={item.id}item={item}></TodoItem>
})
}
</div>
);
}
}
//connect目的是问“天”要数据,要通天。
//“通天”是比喻,就是说要问store要数据
exportdefaultconnect(
(state)=>{
return{
todos:state.todos
}
}
)(TodoBd);
TodoItem.js
importReactfrom'react';
exportdefaultclassTodoItemextendsReact.Component{
constructor(props){
super(props);
}
render(){
return(
<divclassName="todoItem">
<inputtype="checkbox"checked={this.props.item.done}/>
<span>{this.props.item.title}</span>
<button>删除</button>
</div>
);
}
}
TodoHd.js增加待办事项
importReactfrom'react';
import{connect}from"react-redux";
classTodoHdextendsReact.Component{
constructor(props){
super(props);
}
render(){
return(
<div>
<inputtype="text"ref="titleTxt"/>
<buttononClick={()=>{
this.props.addTodo(this.refs.titleTxt.value);
this.refs.titleTxt.value="";
}}>添加</button>
</div>
);
}
}
//这个组件要通天的目的不是要数据,而是改变数据
exportdefaultconnect(
null,
(dispatch)=>{
return{
addTodo(title){
dispatch({"type":"ADDTODO",title})
}
}
}
)(TodoHd);
reducers/index.js写可预测状态
constinitObj={
"todos":[
...
],
}
exportdefault(state=initObj,action)=>{
if(action.type=="ADDTODO"){
return{
...state,
"todos":[
...state.todos,
{
"id":state.todos.reduce((a,b)=>{
returnb.id>a?b.id:a;
},0)+1,
"title":action.title,
"done":false
}
]
}
}
returnstate;
}
TodoFt.js
importReactfrom"react";
import{connect}from"react-redux";
classTodoFtextendsReact.Component{
constructor(){
super();
}
render(){
return<div>
当前:共{this.props.todos.length}条信息--
已做{this.props.todos.filter(item=>item.done).length}条--
未做{this.props.todos.filter(item=>!item.done).length}条
</div>
}
}
exportdefaultconnect(
(state)=>{
return{
todos:state.todos
}
}
)(TodoFt)
示例代码
因为TodoItem这个组件是不通天的,所以TodoItem是不能自己独立dispatch到store的。
此时就需要TodoBd帮助,因为TodoBd是通天。
这是套路:所有被循环语句map出来的组件,一律不通天,数据父亲给,改变store的能力父亲给。
TodoBd.js组件引入了TodoItem.js组件,因为TodoItem组件是被map出来的,所以信息要传给每一个TodoItem,而不是让TodoItem自己通天拿数据。
TodoBd.js
importReactfrom'react';
import{connect}from"react-redux";
importTodoItemfrom"./TodoItem.js";
classTodoBdextendsReact.Component{
constructor(props){
super(props);
}
render(){
//根据全局的show属性来决定当前todos数组
if(this.props.show=="ALL"){
vartodos=this.props.todos;
}elseif(this.props.show=="ONLYDONE"){
vartodos=this.props.todos.filter(item=>item.done);
}elseif(this.props.show=="ONLYUNDONE"){
vartodos=this.props.todos.filter(item=>!item.done);
}
return(
<div>
{
todos.map(item=>{
return<TodoItem
key={item.id}
item={item}
delTodo={this.props.delTodo.bind(this)}
changeTodo={this.props.changeTodo.bind(this)}
></TodoItem>
})
}
</div>
);
}
}
exportdefaultconnect(
(state)=>{
return{
todos:state.todos,
show:state.show
}
},
(dispatch)=>{
return{
delTodo(id){
dispatch({"type":"DELTODO",id});
},
changeTodo(id,k,v){
dispatch({"type":"CHANGETODO",id,k,v});
}
}
}
)(TodoBd);
TodoItem.js
importReactfrom'react';
exportdefaultclassTodoItemextendsReact.Component{
constructor(props){
super(props);
this.state={
"onEdit":false
}
}
render(){
const{id,title,done}=this.props.item;
return(
<div>
<input
type="checkbox"checked={done}
onChange={(e)=>{
this.props.changeTodo(id,"done",e.target.checked)
}}
/>
{
this.state.onEdit
?
<input
type="text"
defaultValue={title}
onBlur={(e)=>{
this.props.changeTodo(id,"title",e.target.value)
this.setState({"onEdit":false})
}}
/>
:
<spanonDoubleClick={()=>{this.setState({"onEdit":true})}}>
{title}
</span>
}
<buttononClick={()=>{this.props.delTodo(id)}}>删除</button>
</div>
);
}
}
index.js
constinitObj={
"todos":[
...
],
"show":"ALL"//ALL、ONLYDONE、ONLYUNDONE
}
exportdefault(state=initObj,action)=>{
if(action.type=="ADDTODO"){
...
}elseif(action.type=="DELTODO"){
return{
...state,
"todos":state.todos.filter(item=>item.id!=action.id)
}
}elseif(action.type=="CHANGETODO"){
return{
...state,
"todos":state.todos.map(item=>{
//如果遍历到的item项和传入的aciton的id项不一样,此时返回原item
if(item.id!=action.id)returnitem;
//否则返回修改之后的item
return{
...item,
[action.k]:action.v
}
})
}
}elseif(action.type=="CHANGESHOW"){
return{
...state,
"show":action.show
}
}
returnstate;
}
TodoFt.js
importReactfrom'react';
import{connect}from"react-redux";
importclassnamesfrom"classnames";
classTodoFtextendsReact.Component{
constructor(props){
super(props);
}
render(){
return(
<div>
<p>
当前共{this.props.todos.length}条信息
做完{this.props.todos.filter(item=>item.done).length}条
未做{this.props.todos.filter(item=>!item.done).length}条
</p>
<p>
<buttonclassName={classnames({"cur":this.props.show=='ALL'})}
onClick={()=>{this.props.changeShow('ALL')}}>查看全部
</button>
<buttonclassName={classnames({"cur":this.props.show=='ONLYDONE'})}
onClick={()=>{this.props.changeShow('ONLYDONE')}}>仅看已做
</button>
<buttonclassName={classnames({"cur":this.props.show=='ONLYUNDONE'})}
onClick={()=>{this.props.changeShow('ONLYUNDONE')}}>仅看未做
</button>
</p>
</div>
);
}
}
exportdefaultconnect(
(state)=>{
return{
todos:state.todos,
show:state.show
}
},
(dispatch)=>{
return{
changeShow(show){
dispatch({"type":"CHANGESHOW",show})
}
}
}
)(TodoFt);
四、logger插件
redux-logger用来辅助开发。
npminstall--saveredux-logger
改变main.js:
importReactfrom"react";
importReactDOMfrom"react-dom";
import{createStore,applyMiddleware}from"redux";
import{Provider}from"react-redux";
importloggerfrom"redux-logger";
importAppfrom"./App.js";
//引入reducer
importreducerfrom"./reducers/index.js";
//创建store
conststore=createStore(reducer,applyMiddleware(logger));
ReactDOM.render(
<Providerstore={store}>
<App></App>
</Provider>
,
document.getElementById("app-container")
);
也可以使用redux-devtools这个插件。
npminstall--save-devredux-devtools
npminstall--save-devredux-devtools-log-monitor
npminstall--save-devredux-devtools-dock-monitor
npminstall--save-devredux-devtools-chart-monitor
文档:
https://github.com/reduxjs/redux-devtools
https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md
五、combineReducers和bindActionCreators
一个网页的应用程序可能是多个reducer,合并为一个reducer,比如counter和todo的reducer。
Redux提供的combineReducers方法,用于reducer的拆分,只要定义各个子reducer函数,然后用这个方法,将它们合成一个大的reducer。
Redux提供的bindActionCreators方法,用于通过dispatch将action包裹起来,这条可以通过bindActionCreators创建的方法,直接调用dispatch(action)“隐式调用”。
5.1combineReducers
reducers/counter.js就是一个普通的纯函数:
exportdefault(state={"v":10},action)=>{
returnstate;
}
reducers/todo.js提供的数据:
constinitObj={
"todos":[
{"id":1,"title":"吃饭","done":false},
{"id":2,"title":"睡觉","done":false},
{"id":3,"title":"打豆豆","done":true}
]
};
exportdefault(state=initObj,action)=>{
returnstate
}
示例代码
reducers/index.js要用redux提供的combineReducers来进行智能合并
import{combineReducers}from"redux";
importcounterfrom"./counter.js";
importtodosfrom"./todos.js";
//暴露合并的reducer
exportdefaultcombineReducers({
counter,
todos
})
main.js
importReactfrom"react";
importReactDOMfrom"react-dom";
import{createStore}from"redux";
import{Provider}from"react-redux";
importAppfrom"./containers/App.js";
//引入reducer
importreducerfrom"./reducers";
//创建Reduxstore来存放应用的状态。
conststore=createStore(reducer);
ReactDOM.render(
<Providerstore={store}>
<App></App>
</Provider>,
document.getElementById("app")
);
containers/App.js组件使用数据
importReactfrom'react';
import{connect}from"react-redux";
classAppextendsReact.Component{
constructor(){
super();
}
render(){
return(
<div>
<h1>{this.props.v}</h1>
</div>
);
}
}
exportdefaultconnect(
({counter})=>({
v:counter.v
})
)(App);
components/TodoList/index.js组件
importReactfrom"react";
import{connect}from"react-redux";
classTodoListextendsReact.Component{
constructor(){
super();
}
render(){
return(
<div>
<h1>我是TodoList</h1>
{
this.props.todos.map(item=>{
return<pkey={item.id}>{item.title}</p>
})
}
</div>
)
}
};
exportdefaultconnect(
({todos:{todos}})=>{
return{
todos
}
}
)(TodoList)
containers/App.js引入组件:
importReactfrom'react';
importTodoListfrom"../components/todolist/index.js";
importCounterfrom"../components/counter/index.js";
exportdefaultclassAppextendsReact.Component{
constructor(props){
super(props);
}
render(){
return(
<div>
<Counter></Counter>
<TodoList></TodoList>
</div>
);
}
}
5.2bindActionCreators
bindActionCreators会将action和dispatch绑定并返回一个对象,这个对象会作为props的一部分传入组件中。
bindActionCreators主要作用:一般情况下,可以通过Provider将store通过React的connext属性向下传递,bindActionCreators的唯一用处就是需要传递actioncreater到子组件,并且该子组件并没有接收到父组件上传递的store和dispatch。
官方的文件夹结构:https://github.com/reduxjs/redux/tree/master/examples/todomvc/src
actions/counterActions.js:新建actions文件夹存放type
//我们把return一个action的函数叫做“actioncreator”
//所以这个文件向外暴露了几个动作
exportconstadd=()=>({type:"ADD"})
exportconstminus=()=>({type:"MINUS"})
exportconstcheng=()=>({type:"CHENG"})
exportconstchu=()=>({type:"CHU"})
counter/index.js计数器组件
importReactfrom'react';
import{bindActionCreators}from"redux";
import{connect}from"react-redux";
import*ascounterActionsfrom"../../actions/counterActions.js";
classCounterextendsReact.Component{
constructor(props){
super(props);
}
render(){
return(
<div>
<h1>Counter:{this.props.v}</h1>
<buttononClick={()=>{this.props.counterActions.add()}}>加</button>
<buttononClick={()=>{this.props.counterActions.minus()}}>减</button>
<buttononClick={()=>{this.props.counterActions.cheng()}}>乘</button>
<buttononClick={()=>{this.props.counterActions.chu()}}>除</button>
</div>
);
}
}
exportdefaultconnect(
({counter})=>({
v:counter.v
}),
(dispatch)=>({
//这里的dispatch,等同于store中的store.dispatch,用于组合action
counterActions:bindActionCreators(counterActions,dispatch)
})
)(Counter);
app/reducers/counter.js:
import{ADD,MINUS,CHENG,CHU}from"../constants/COUNTER.js";
exportdefault(state={"v":0},action)=>{
if(action.type=="ADD"){
return{
...state,
"v":state.v+1
}
}elseif(action.type=="MINUS"){
return{
...state,
"v":state.v-1
}
}elseif(action.type=="CHENG"){
return{
...state,
"v":state.v*2
}
}elseif(action.type=="CHU"){
return{
...state,
"v":state.v/2
}
}
returnstate;
}
示例代码
todolist/index.js
importReactfrom'react';
importTodoHdfrom"./TodoHd.js";
importTodoBdfrom"./TodoBd.js";
exportdefaultclassTodoListextendsReact.Component{
constructor(props){
super(props);
}
render(){
return(
<div>
<h1>TodoList</h1>
<TodoHd></TodoHd>
<TodoBd></TodoBd>
</div>
);
}
}
TodoHd.js
importReactfrom'react';
import{bindActionCreators}from"redux";
import{connect}from"react-redux";
import*astodoActionsfrom"../../actions/todoActions.js";
classTodoHdextendsReact.Component{
constructor(props){
super(props);
}
render(){
return(
<div>
<inputtype="text"ref="titleTxt"/>
<buttononClick={()=>{this.props.todoActions.add(this.refs.titleTxt.value)}}
>添加
</button>
</div>
);
}
}
exportdefaultconnect(
null,
(dispatch)=>({
todoActions:bindActionCreators(todoActions,dispatch)
})
)(TodoHd);
TodoBd.js
importReactfrom'react';
import{bindActionCreators}from"redux";
import{connect}from"react-redux";
import*astodoActionsfrom"../../actions/todoActions.js";
classTodoBdextendsReact.Component{
constructor(props){
super(props);
}
render(){
return(
<div>
{
this.props.todos.map(item=>{
return<pkey={item.id}>
{item.title}
<buttononClick={()=>{this.props.todoActions.del(item.id)}}>
删除
</button>
</p>
})
}
</div>
);
}
}
exportdefaultconnect(
({todo})=>({
todos:todo.todos
}),
(dispatch)=>({
todoActions:bindActionCreators(todoActions,dispatch)
})
)(TodoBd);
为了防止action的type命名冲突,此时要单独存放在const文件夹中:
app\constants\COUNTER.js
exportconstADD="ADD_COUNTER";
exportconstMINUS="MINUS_COUNTER";
exportconstCHENG="CHENG_COUNTER";
exportconstCHU="CHU_COUNTER";
app\constants\TODO.js
exportconstADD="ADD_TODO";
exportconstDEL="DEL_TODO";
然后就可以在以下文件中,引入以上常量,然后使用大写的常量替换type字符串
lactions中的counterActions.js和todoActions.js
lreducers中的todo.js和counter.js
actions/TodoActions.js
import{ADD,DEL}from"../constants/TODO.js";
exportconstadd=(title)=>({"type":ADD,title});
exportconstdel=(id)=>({"type":DEL,id});
actions/counterActions.js:
import{ADD,MINUS,CHENG,CHU}from"../constants/COUNTER.js";
exportconstadd=()=>({"type":ADD});
exportconstminus=()=>({"type":MINUS});
exportconstcheng=()=>({"type":CHENG});
exportconstchu=(n)=>({"type":CHU,n});
reducers/todo.js
import{ADD,DEL}from"../constants/TODO.js";
constinitObj={
"todos":[
{"id":1,"title":"吃饭","done":false},
{"id":2,"title":"睡觉","done":false},
{"id":3,"title":"打豆豆","done":false}
]
}
exportdefault(state=initObj,action)=>{
if(action.type==ADD){
return{
...state,
"todos":[
...state.todos,
{
"id":state.todos.reduce((a,b)=>{returnb.id>a?b.id:a},0)+1,
"title":action.title,
"done":action.done
}
]
}
}elseif(action.type==DEL){
return{
...state,
"todos":state.todos.filter(item=>item.id!=action.id)
}
}
returnstate;
}
reducers/counter.js
import{ADD,MINUS,CHENG,CHU}from"../constants/COUNTER.js";
exportdefault(state={"v":0},action)=>{
if(action.type==ADD){
return{
...state,
"v":state.v+1
}
}elseif(action.type==MINUS){
return{
...state,
"v":state.v-1
}
}elseif(action.type==CHENG){
return{
...state,
"v":state.v*2
}
}elseif(action.type==CHU){
return{
...state,
"v":state.v/action.n
}
}
returnstate;
如需转载,请注明文章出处和来源网址:http://www.divcss5.com/html/h56662.shtml