前端笔记之React(五)Redux深入浅出
一、redux整体感知
redux是javascript状态管理容器,提供了可被预测状态的状态管理容器。来自于flux思想,facebook基于flux思想,在2015年推出redux库。
中文网站:
官方git:
首先要引redux.js包,这个包提供了redux对象,这个对象可以调用redux.createstore()方法。
<body> <h1 id="info"></h1> <button id="btn1">加</button> <button id="btn2">减</button> <button id="btn3">乘</button> <button id="btn4">除</button> <script type="text/javascript" src="redux.min.js"></script> <script type="text/javascript"> //这是一个 reducer,形式为 (state, action) => state 的纯函数。 //纯函数:函数内部,不改变传入的参数,只return新值。 //描述了 action 如何把 state 转变成下一个 state。 const reducer = (state = {"v" : 10} , action)=>{ if(action.type == "add"){ return {"v" : state.v + 1}; }else if(action.type == "minus"){ return {"v" : state.v - 1}; }else if(action.type == "cheng2"){ return {"v" : state.v * 2} } return state; } //创建 redux store 来存放应用的状态。 //store翻译为“仓库”,这是一个存放数据并且可以操作数据的东西 const store = redux.createstore(reducer); //创建一个视图函数,并且将store显示到视图上。 const render = ()=>{ //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。
const reducer = (state = {"v" : 0} , action)=>{ if(action.type == "add"){ return {"v" : state.v + 1}; }else if(action.type == "minus"){ return {"v" : state.v - 1}; } return state; }
2)这个函数提供了可被预测的功能。
reducer中的state,就像被关进了保险箱。任何对这个state的变化,只能是通过dispatch一个action来改变的。换句话说,只有dispatch一个action才能改变这个保险箱中的数据。
在reducer函数中,用if语句来表示对state可能发生变化的罗列,只有罗列在楼层里面的改变,才会发生:
if(action.type==""){ return 新的state }else if(action.type ==""){ return 新的state }else if(action.type ==""){ return 新的state }
创建store仓库,这个仓库创建时需要提供reducer,所以可以认为store就是reducer,reducer就是store。
const store = 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> <h1 id="info"></h1> <button id="btn1">加</button> <input type="text" id="txt"> <button id="btn2">加输入的</button> <script type="text/javascript" src="redux.min.js"></script> <script type="text/javascript"> const reducer = (state = {"v" : 10}, {type, data})=>{ if(type == "add"){ return {"v" : state.v + action.data} return {"v" : state.v + data} } return state; } const store = redux.createstore(reducer); const render = ()=>{ 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(){ var a = parseint(document.getelementbyid("txt").value); //载荷payload。 store.dispatch({"type":"add" , data:a}); } </script> </body>
唯一可以改变state的方式dispatch一个action
案例3:添加数组
<body> <div id="box"> <p><input type="text" id="name"></p> <p><input type="text" id="age"></p> <button id="btn">增加</button> <ul id="list"> </ul> </div> <script type="text/javascript" src="redux.min.js"></script> <script type="text/javascript"> //初始数据 const initobj = { "arr" : [ {"name" : "小明" , "age" : 12}, {"name" : "小红" , "age" : 13}, {"name" : "小强" , "age" : 14} ] }; //reducer函数 const reducer = (state = initobj, {type , name , age}) => { if(type == "add_student"){ //不能push改变传入的原数组,所以要返回新数组 return { "arr" : [ {name , age} , ...state.arr ] } } return state; } //创建store const store = redux.createstore(reducer); //视图函数 const render = function(){ //清空ul document.getelementbyid("list").innerhtml = ""; //创建li for(var i = 0 ; i < store.getstate().arr.length ; i++){ var li = document.createelement("li"); var storearr = store.getstate().arr[i] li.innerhtml = storearr.name + storearr.age+"岁" document.getelementbyid("list").appendchild(li); } } render();//运行视图函数 store.subscribe(render);//注册到视图 document.getelementbyid("btn").onclick = function(){ var name = document.getelementbyid("name").value; var age = document.getelementbyid("age").value; //发出action,这是唯一能够改变store的途径 store.dispatch({"type" : "add_student", name, age }) } </script> </body>
案例4,加深练习:
<body> <h1 id="info"></h1> <h1 id="info2"></h1> <button id="btn">+</button> <script type="text/javascript" src="redux.min.js"></script> <script type="text/javascript"> var initobj = { "a" : { "b" : { "c" : { "v" : 10 } }, "m" : 8 } } const reducer = (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 } } } } } return state; } const store = redux.createstore(reducer); const render = ()=>{ 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()函数。
安装两个依赖:
npm install --save redux react-redux
创建reducers文件夹,创建index.js,这个文件暴露一个纯函数:
reducers/index.js:
export default (state = { v : 10}, action) => { return state; }
main.js入口文件:
import react from "react"; import reactdom from "react-dom"; import {createstore} from "redux"; import app from "./app.js"; import reducer from "./reducers"; //reducer函数 //创建 redux store 来存放应用的状态。 const store = 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)中
import react from "react"; import reactdom from "react-dom"; import {createstore} from "redux"; import {provider} from "react-redux"; import app from "./app.js"; import reducer from "./reducers"; //创建store仓库 const store = createstore(reducer); reactdom.render( <provider store={store}> <app></app> </provider>, document.getelementbyid('app') );
组件中要获得全局store的值,需要用connect函数,connect提供连接react组件与redux store的作用。
connect([mapstatetoprops], [mapdispatchtoprops])
connect()的第一个参数:mapstatetoprops这个函数的第一个参数就是redux的store,会自动将store的数据作为props绑定到组件上。
connect()的第二个参数:mapdispatchtoprops它的功能是,将action作为props绑定到组件上
通俗理解,使用connect可以把state和dispatch绑定到react组件,使得组件可以访问到redux的数据。
常看到下面这种写法:
export default connect()(app)
app.js
import react from "react"; import {connect} from "react-redux"; class app extends react.component { constructor() { super(); } render(){ return <div> <h1>{this.props.v}</h1> </div> } } export default connect( //这个函数return的对象的值,将自动成为组件的props。 (state) => { return { v : state.v } } )(app);
一旦connect()(app); 连接某个组件,此时这个组件就会:当全局store发送改变时,如同自己的props发生改变一样,从而进行视图更新。
在app.js写两个按钮,可以加1,减1:
import react from "react"; import {connect} from "react-redux"; class app extends react.component { constructor() { super(); } render(){ return <div> <h1>{this.props.v}</h1> <button onclick={()=>{this.props.add()}}>+</button> <button onclick={()=>{this.props.minus()}}>-</button> </div> } } export default connect( (state) => { return { v : state.v } }, (dispatch) => { return { add(){ dispatch({"type" : "add"}); }, minus(){ dispatch({"type" : "minus"}); } } } )(app);
reducers/index.js提供可预测状态
export default (state = {"v" : 10} , action) => { if(action.type == "add"){ return { "v" : state.v + 1 }; }else if(action.type == "minus"){ return { "v" : state.v - 1 }; } return state; }
学生管理系统小案例:
reducers/index.js
const initobj = { "arr" : [ {"id" : 1, "name" : "小明" , "age" : 12}, {"id" : 2, "name" : "小红" , "age" : 13}, {"id" : 3, "name" : "小刚" , "age" : 14} ] } export default (state = initobj, {type, age, name, id})=>{ if(type == "add_student"){ return { ...state , "arr" : [ ...state.arr , { "id" : state.arr.reduce((a,b)=>{ return b.id > a ? b.id : a; },0) + 1, name, age } ] } }else if(type == "del_student"){ return { ...state, "arr" : state.arr.filter(item=>item.id != id) } } return state; }
app.js
import react from "react"; import {connect} from "react-redux"; class app extends react.component { constructor() { super(); } //增加学生 add(){ var name = this.refs.name.value; var age = this.refs.age.value; this.props.addstudent(name, age) } render(){ return <div> <p><input type="text" ref="name"/></p> <p><input type="text" ref="age"/></p> <button onclick={()=>{this.add()}}>增加学生</button> { this.props.arr.map((item,index)=>{ return <p key={item.id}> {item.id} {item.name} {item.age}岁 <button onclick={()=>{this.props.delstudent(item.id)}}>删除</button> </p> }) } </div> } } export default connect( //(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文档:
不管应用程序有多大,store只有一个,它就像天神一样“照耀”所有组件,但默认情况下所有组件是不能得到store的数据的,哪个组件要拿数据,就要connect一下,另外app最大组件确实包裹着所有组件,但不代表app组件连接了就代表其他组件也连接了。
三、redux编程-todolist
在reducers/index.js中根据项目情况创建reducer:
const initobj = { "todos": [ {"id" : 1, "title" : "吃饭", "done" : false}, {"id" : 2, "title" : "睡觉", "done" : false}, {"id" : 3, "title" : "打豆豆","done" : false} ] } export default (state = initobj, action) => { return state; }
分别创建todohd.js、todobd.js、todoft.js三个组件:
import react from "react"; export default class todohd extends react.component { constructor() { super(); } render() { return <div> <h1>我是todohd组件</h1> </div> } }
app.js引入组件
import react from "react"; import {connect} from "react-redux"; import todohd from "./components/todohd.js"; import todobd from "./components/todobd.js"; import todoft from "./components/todoft.js"; class app extends react.component { constructor() { super(); } render() { return <div> <todohd></todohd> <todobd></todobd> <todoft></todoft> </div> } } export default connect()(app)
import react from 'react'; import {connect} from "react-redux"; import todoitem from "./todoitem.js"; class todobd extends react.component { constructor(props){ super(props); } render() { return ( <div> //{json.stringify(this.props.todos)} { this.props.todos.map(item=>{ //return <div key={item.id}>{item.title}</div> return <todoitem key={item.id} item={item}></todoitem> }) } </div> ); } } //connect目的是问“天”要数据,要通天。 //“通天”是比喻,就是说要问store要数据 export default connect( (state)=>{ return { todos : state.todos } } )(todobd);
todoitem.js
import react from 'react'; export default class todoitem extends react.component { constructor(props) { super(props); } render() { return ( <div classname="todoitem"> <input type="checkbox" checked={this.props.item.done}/> <span>{this.props.item.title}</span> <button>删除</button> </div> ); } }
todohd.js增加待办事项
import react from 'react'; import {connect} from "react-redux"; class todohd extends react.component { constructor(props) { super(props); } render() { return ( <div> <input type="text" ref="titletxt"/> <button onclick={()=>{ this.props.addtodo(this.refs.titletxt.value); this.refs.titletxt.value = ""; }}>添加</button> </div> ); } } //这个组件要通天的目的不是要数据,而是改变数据 export default connect( null , (dispatch)=>{ return { addtodo(title){ dispatch({"type":"addtodo", title}) } } } )(todohd);
reducers/index.js写可预测状态
const initobj = { "todos" : [ ... ], } export default (state = initobj, action)=>{ if(action.type == "addtodo"){ return { ...state, "todos" : [ ...state.todos, { "id" : state.todos.reduce((a,b)=>{ return b.id > a ? b.id : a; },0) + 1, "title" : action.title, "done" : false } ] } } return state; }
todoft.js
import react from "react"; import {connect} from "react-redux"; class todoft extends react.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> } } export default connect( (state)=>{ return { todos:state.todos } } )(todoft)
因为todoitem这个组件是不通天的,所以todoitem是不能自己独立dispatch到store的。
此时就需要todobd帮助,因为todobd是通天。
这是套路:所有被循环语句map出来的组件,一律不通天,数据父亲给,改变store的能力父亲给。
todobd.js组件引入了todoitem.js组件,因为todoitem组件是被map出来的,所以信息要传给每一个todoitem,而不是让todoitem自己通天拿数据。
todobd.js
import react from 'react'; import {connect} from "react-redux"; import todoitem from "./todoitem.js"; class todobd extends react.component { constructor(props) { super(props); } render() { //根据全局的show属性来决定当前todos数组 if(this.props.show == "all"){ var todos = this.props.todos; }else if(this.props.show == "onlydone"){ var todos = this.props.todos.filter(item=>item.done); }else if(this.props.show == "onlyundone"){ var todos = 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> ); } } export default connect( (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
import react from 'react'; export default class todoitem extends react.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}) }} /> : <span ondoubleclick={()=>{this.setstate({"onedit":true})}}> {title} </span> } <button onclick={()=>{this.props.deltodo(id)}}>删除</button> </div> ); } }
index.js
const initobj = { "todos" : [ ... ], "show" : "all" //all、onlydone、onlyundone } export default (state = initobj, action) => { if(action.type == "addtodo"){ ... }else if(action.type == "deltodo"){ return { ...state, "todos" : state.todos.filter(item => item.id != action.id) } }else if(action.type == "changetodo"){ return { ...state, "todos" : state.todos.map(item => { //如果遍历到的item项和传入的aciton的id项不一样,此时返回原item if(item.id != action.id) return item; //否则返回修改之后的item return { ...item , [action.k] : action.v } }) } }else if(action.type == "changeshow"){ return { ...state, "show" : action.show } } return state; }
todoft.js
import react from 'react'; import {connect} from "react-redux"; import classnames from "classnames"; class todoft extends react.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> <button classname={classnames({"cur":this.props.show == 'all'})}
onclick={()=>{this.props.changeshow('all')}}>查看全部 </button> <button classname={classnames({"cur":this.props.show == 'onlydone'})} onclick={()=>{this.props.changeshow('onlydone')}}>仅看已做 </button> <button classname={classnames({"cur":this.props.show == 'onlyundone'})} onclick={()=>{this.props.changeshow('onlyundone')}}>仅看未做 </button> </p> </div> ); } } export default connect( (state) => { return { todos : state.todos , show : state.show } }, (dispatch) => { return { changeshow(show){ dispatch({"type" : "changeshow" , show}) } } } )(todoft);
四、logger插件
redux-logger用来辅助开发。
npm install --save redux-logger
改变main.js:
import react from "react"; import reactdom from "react-dom"; import {createstore , applymiddleware} from "redux"; import {provider} from "react-redux"; import logger from "redux-logger"; import app from "./app.js"; //引入reducer import reducer from "./reducers/index.js"; //创建store const store = createstore(reducer , applymiddleware(logger)); reactdom.render( <provider store={store}> <app></app> </provider> , document.getelementbyid("app-container") );
也可以使用redux-devtools这个插件。
npm install --save-dev redux-devtools npm install --save-dev redux-devtools-log-monitor npm install --save-dev redux-devtools-dock-monitor npm install --save-dev redux-devtools-chart-monitor
文档:
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.1 combinereducers
reducers/counter.js就是一个普通的纯函数:
export default (state = {"v" : 10},action)=>{ return state; }
reducers/todo.js提供的数据:
const initobj = { "todos": [ { "id": 1, "title": "吃饭", "done": false }, { "id": 2, "title": "睡觉", "done": false }, { "id": 3, "title": "打豆豆", "done": true } ] }; export default (state = initobj, action) => { return state }
reducers/index.js要用redux提供的combinereducers来进行智能合并
import { combinereducers } from "redux"; import counter from "./counter.js"; import todos from "./todos.js"; //暴露合并的reducer export default combinereducers({ counter, todos })
main.js
import react from "react"; import reactdom from "react-dom"; import {createstore} from "redux"; import {provider} from "react-redux"; import app from "./containers/app.js"; //引入reducer import reducer from "./reducers"; // 创建 redux store 来存放应用的状态。 const store = createstore(reducer); reactdom.render( <provider store={store}> <app></app> </provider>, document.getelementbyid("app") );
containers/app.js组件使用数据
import react from 'react'; import {connect} from "react-redux"; class app extends react.component { constructor(){ super(); } render() { return ( <div> <h1>{this.props.v}</h1> </div> ); } } export default connect( ({counter}) => ({ v : counter.v }) )(app);
components/todolist/index.js组件
import react from "react"; import { connect } from "react-redux"; class todolist extends react.component { constructor(){ super(); } render() { return ( <div> <h1>我是todolist</h1> { this.props.todos.map(item=>{ return <p key={item.id}>{item.title}</p> }) } </div> ) } }; export default connect( ({todos: {todos}})=>{ return { todos } } )(todolist)
containers/app.js引入组件:
import react from 'react'; import todolist from "../components/todolist/index.js"; import counter from "../components/counter/index.js"; export default class app extends react.component { constructor(props) { super(props); } render() { return ( <div> <counter></counter> <todolist></todolist> </div> ); } }
5.2 bindactioncreators
bindactioncreators会将action和dispatch绑定并返回一个对象,这个对象会作为props的一部分传入组件中。
bindactioncreators主要作用:一般情况下,可以通过provider将store通过react的connext属性向下传递,bindactioncreators的唯一用处就是需要传递action creater到子组件,并且该子组件并没有接收到父组件上传递的store和dispatch。
官方的文件夹结构:
actions/counteractions.js:新建actions文件夹存放type
// 我们把return一个action的函数叫做“action creator” // 所以这个文件向外暴露了几个动作 export const add = () => ({ type: "add" }) export const minus = () => ({ type: "minus" }) export const cheng = () => ({ type: "cheng" }) export const chu = () => ({ type: "chu" })
counter/index.js计数器组件
import react from 'react'; import {bindactioncreators} from "redux"; import {connect} from "react-redux"; import * as counteractions from "../../actions/counteractions.js"; class counter extends react.component { constructor(props) { super(props); } render() { return ( <div> <h1>counter : {this.props.v}</h1> <button onclick={()=>{this.props.counteractions.add()}}>加</button> <button onclick={()=>{this.props.counteractions.minus()}}>减</button> <button onclick={()=>{this.props.counteractions.cheng()}}>乘</button> <button onclick={()=>{this.props.counteractions.chu()}}>除</button> </div> ); } } export default connect( ({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"; export default (state = {"v" : 0} , action) => { if(action.type == "add"){ return { ...state , "v" : state.v + 1 } }else if(action.type == "minus"){ return { ...state , "v" : state.v - 1 } }else if(action.type == "cheng"){ return { ...state , "v" : state.v * 2 } }else if(action.type == "chu"){ return { ...state , "v" : state.v / 2 } } return state; }
todolist/index.js
import react from 'react'; import todohd from "./todohd.js"; import todobd from "./todobd.js"; export default class todolist extends react.component { constructor(props) { super(props); } render() { return ( <div> <h1>todolist</h1> <todohd></todohd> <todobd></todobd> </div> ); } }
todohd.js
import react from 'react'; import {bindactioncreators} from "redux"; import {connect} from "react-redux"; import * as todoactions from "../../actions/todoactions.js"; class todohd extends react.component { constructor(props) { super(props); } render() { return ( <div> <input type="text" ref="titletxt"/> <button onclick={()=>{this.props.todoactions.add(this.refs.titletxt.value)}} >添加 </button> </div> ); } } export default connect( null , (dispatch) => ({ todoactions : bindactioncreators(todoactions , dispatch) }) )(todohd);
todobd.js
import react from 'react'; import {bindactioncreators} from "redux"; import {connect} from "react-redux"; import * as todoactions from "../../actions/todoactions.js"; class todobd extends react.component { constructor(props) { super(props); } render() { return ( <div> { this.props.todos.map(item=>{ return <p key={item.id}> {item.title} <button onclick={()=>{this.props.todoactions.del(item.id)}}> 删除 </button> </p> }) } </div> ); } } export default connect( ({todo}) => ({ todos : todo.todos }) , (dispatch) => ({ todoactions : bindactioncreators(todoactions , dispatch) }) )(todobd);
为了防止action的type命名冲突,此时要单独存放在const文件夹中:
app\constants\counter.js
export const add = "add_counter"; export const minus = "minus_counter"; export const cheng = "cheng_counter"; export const chu = "chu_counter";
app\constants\todo.js
export const add = "add_todo"; export const del = "del_todo";
然后就可以在以下文件中,引入以上常量,然后使用大写的常量替换type字符串
l actions中的counteractions.js和todoactions.js
l reducers中的todo.js和counter.js
actions/todoactions.js
import {add , del} from "../constants/todo.js"; export const add = (title) => ({"type" : add, title}); export const del = (id) => ({"type" : del, id});
actions/counteractions.js:
import {add , minus , cheng , chu} from "../constants/counter.js"; export const add = () => ({"type" : add}); export const minus = () => ({"type" : minus}); export const cheng = () => ({"type" : cheng}); export const chu = (n) => ({"type" : chu , n});
reducers/todo.js
import {add , del} from "../constants/todo.js"; const initobj = { "todos" : [ {"id" : 1 , "title" : "吃饭" , "done" : false}, {"id" : 2 , "title" : "睡觉" , "done" : false}, {"id" : 3 , "title" : "打豆豆" , "done" : false} ] } export default (state = initobj , action) => { if(action.type == add){ return { ...state , "todos" : [ ...state.todos , { "id" : state.todos.reduce((a,b)=>{return b.id > a ? b.id : a},0) + 1, "title": action.title, "done" : action.done } ] } }else if(action.type == del){ return { ...state , "todos" : state.todos.filter(item => item.id != action.id) } } return state; }
reducers/counter.js
import {add , minus , cheng , chu} from "../constants/counter.js"; export default (state = {"v" : 0} , action) => { if(action.type == add){ return { ...state , "v" : state.v + 1 } }else if(action.type == minus){ return { ...state , "v" : state.v - 1 } }else if(action.type == cheng){ return { ...state , "v" : state.v * 2 } }else if(action.type == chu){ return { ...state , "v" : state.v / action.n } } return state; }
上一篇: Linux的常用基础命令
下一篇: Golang 读写文件
推荐阅读
-
前端笔记之React(八)上传&图片裁切
-
react与redux通信之hook-前端江湖-SegmentFault思否
-
前端笔记之Vue(五)TodoList实战&拆分store&跨域&练习代理跨域
-
前端笔记之React(七)redux-saga&Dva&路由
-
前端笔记之React(六)ES6的Set和Map&immutable和Ramda和lodash&redux-thunk
-
前端笔记之React(五)Redux深入浅出
-
前端笔记之React(一)初识React&组件&JSX语法
-
前端笔记之React(八)上传&图片裁切
-
前端笔记之React(二)组件内部State&React实战&表单元素的受控
-
前端笔记知识点整合之JavaScript(五)关于数组和字符串那点事