一、Redux整体感知

Redux是JavaScript状态管理容器,提供了可被预测状态的状态管理容器。来自于Flux思想,Facebook基于Flux思想,在2015年推出Redux库。

中文网站:http://www.redux.org.cn/

官方git:https://github.com/reduxjs/redux

首先要引redux.js包,这个包提供了Redux对象,这个对象可以调用Redux.createStore()方法。

  1. <body>
  2. <h1 id="info"></h1>
  3. <button id="btn1"></button>
  4. <button id="btn2"></button>
  5. <button id="btn3"></button>
  6. <button id="btn4"></button>
  7.  
  8. <script type="text/javascript" src="redux.min.js"></script>
  9. <script type="text/javascript">
  10. //这是一个 reducer,形式为 (state, action) => state 的纯函数。
  11. //纯函数:函数内部,不改变传入的参数,只return新值。
  12. //描述了 action 如何把 state 转变成下一个 state。
  13. const reducer = (state = {"v" : 10} , action)=>{
  14. if(action.type == "ADD"){
  15. return {"v" : state.v + 1};
  16. }else if(action.type == "MINUS"){
  17. return {"v" : state.v - 1};
  18. }else if(action.type == "CHENG2"){
  19. return {"v" : state.v * 2}
  20. }
  21. return state;
  22. }
  23. //创建 Redux store 来存放应用的状态。
  24. //store翻译为“仓库”,这是一个存放数据并且可以操作数据的东西
  25. const store = Redux.createStore(reducer);
  26.  
  27. //创建一个视图函数,并且将store显示到视图上。
  28. const render = ()=>{
  29. //store的getState()方法可以得到仓库中的数据
  30. document.getElementById("info").innerHTML = store.getState().v;
  31. }
  32. render(); //调用render函数
  33. //要将store注册到视图,这样的话,当store中的数据变化候,就能自动调用render函数
  34. store.subscribe(render);
  35.  
  36. // 应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。
  37. // 惟一改变state的办法是触发action,一个描述发生什么的对象。
  38. // 为了描述 action 如何改变 state 树,你需要编写 reducers。
  39. document.getElementById("btn1").onclick = function(){
  40. // 改变内部 state 惟一方法是 dispatch 一个 action,type表示要做的动作
  41. store.dispatch({"type":"ADD"})
  42. }
  43. document.getElementById("btn2").onclick = function(){
  44. store.dispatch({"type":"MINUS"})
  45. }
  46. document.getElementById("btn3").onclick = function(){
  47. store.dispatch({"type":"CHENG"})
  48. }
  49. </script>
  50. </body>

创建一个叫做reducer的函数,reducer中“维持”一个量,叫state,初始值是{"v" : 0}。

这个函数你必须知道两点:

1)它是一个纯函数,在函数内部不改变{"v" : 0}的,不改变state对象的,只是返回了新的state。

  1. const reducer = (state = {"v" : 0} , action)=>{
  2. if(action.type == "ADD"){
  3. return {"v" : state.v + 1};
  4. }else if(action.type == "MINUS"){
  5. return {"v" : state.v - 1};
  6. }
  7. return state;
  8. }

2)这个函数提供了可被预测的功能。

reducer中的state,就像被关进了保险箱。任何对这个state的变化,只能是通过dispatch一个action来改变的。换句话说,只有dispatch一个action才能改变这个保险箱中的数据

在reducer函数中,用if语句来表示对state可能发生变化的罗列,只有罗列在楼层里面的改变,才会发生:

  1. if(action.type==""){
  2. return 新的state
  3. }else if(action.type ==""){
  4. return 新的state
  5. }else if(action.type ==""){
  6. return 新的state
  7. }

示例代码

创建store仓库,这个仓库创建时需要提供reducer,所以可以认为store就是reducer,reducer就是store。

  1. const store = Redux.createStore(reducer);

reducer是一个纯函数,而store提供了三个方法:

store.subscribe() 注册到视图

store.getState() 得到数据

store.dispatch() 发送action

学习redux和react结合,到时候就不用注册到视图。

然后创建监听:

  1. document.getElementById("btn1").onclick = function(){
  2. store.dispatch({"type":"ADD"})
  3. }
  4. document.getElementById("btn2").onclick = function(){
  5. store.dispatch({"type":"MINUS"})
  6. }
  7. document.getElementById("btn3").onclick = function(){
  8. store.dispatch({"type":"CHENG"})
  9. }

示例代码

点击按钮,store要dispatch出一个action。所谓的action就是一个JSON,这个JSON必须有type属性,值为大写字母。这个action没有任何意义,比如{"type":"ADD"},但reducer认识这个action,可以产生变化!

案例2,添加载荷:

  1. <body>
  2. <h1 id="info"></h1>
  3. <button id="btn1"></button>
  4.  
  5. <input type="text" id="txt">
  6. <button id="btn2">加输入的</button>
  7.  
  8. <script type="text/javascript" src="redux.min.js"></script>
  9. <script type="text/javascript">
  10. const reducer = (state = {"v" : 10}, {type, data})=>{
  11. if(type == "ADD"){
  12. return {"v" : state.v + action.data}
  13.           return {"v" : state.v + data}
  14. }
  15. return state;
  16. }
  17. const store = Redux.createStore(reducer);
  18. const render = ()=>{
  19. document.getElementById("info").innerHTML = store.getState().v;
  20. }
  21. render();
  22. store.subscribe(render);
  23.  
  24. document.getElementById("btn1").onclick = function(){
  25. store.dispatch({ type: 'ADD', data:100});
  26. }
  27.  
  28. document.getElementById("btn2").onclick = function(){
  29. var a = parseInt(document.getElementById("txt").value);
  30. //载荷payload。
  31. store.dispatch({"type":"ADD" , data:a});
  32. }
  33. </script>
  34. </body>

唯一可以改变state的方式dispatch一个action

案例3:添加数组

  1. <body>
  2. <div id="box">
  3. <p><input type="text" id="name"></p>
  4. <p><input type="text" id="age"></p>
  5. <button id="btn">增加</button>
  6. <ul id="List">
  7.  
  8. </ul>
  9. </div>
  10.  
  11. <script type="text/javascript" src="redux.min.js"></script>
  12. <script type="text/javascript">
  13. //初始数据
  14. const initObj = {
  15. "arr" : [
  16. {"name" : "小明" , "age" : 12},
  17. {"name" : "小红" , "age" : 13},
  18. {"name" : "小强" , "age" : 14}
  19. ]
  20. };
  21. //reducer函数
  22. const reducer = (state = initObj, {type , name , age}) => {
  23. if(type == "ADD_STUDENT"){
  24. //不能push改变传入的原数组,所以要返回新数组
  25. return {
  26. "arr" : [
  27. {name , age} ,
  28. ...state.arr
  29. ]
  30. }
  31. }
  32. return state;
  33. }
  34. //创建store
  35. const store = Redux.createStore(reducer);
  36. //视图函数
  37. const render = function(){
  38. //清空ul
  39. document.getElementById("List").innerHTML = "";
  40. //创建li
  41. for(var i = 0 ; i < store.getState().arr.length ; i++){
  42. var li = document.createElement("li");
  43. var storeArr = store.getState().arr[i]
  44. li.innerHTML = storeArr.name + storeArr.age+"岁"
  45. document.getElementById("List").appendChild(li);
  46. }
  47. }
  48. render();//运行视图函数
  49. store.subscribe(render);//注册到视图
  50.  
  51. document.getElementById("btn").onclick = function(){
  52. var name = document.getElementById("name").value;
  53. var age = document.getElementById("age").value;
  54. //发出Action,这是唯一能够改变store的途径
  55. store.dispatch({"type" : "ADD_STUDENT", name, age })
  56. }
  57. </script>
  58. </body>

案例4,加深练习:

  1. <body>
  2. <h1 id="info"></h1>
  3. <h1 id="info2"></h1>
  4. <button id="btn">+</button>
  5.  
  6. <script type="text/javascript" src="redux.min.js"></script>
  7. <script type="text/javascript">
  8. var initObj = {
  9. "a" : {
  10. "b" : {
  11. "c" : {
  12. "v" : 10
  13. }
  14. },
  15. "m" : 8
  16. }
  17. }
  18. const reducer = (state = initObj , action) => {
  19. if(action.type == "ADD"){
  20. return {
  21. ...state,
  22. "a" : {
  23. ...state.a ,
  24. "b" : {
  25. ...state.a.b,
  26. "c" : {
  27. ...state.a.b.c ,
  28. "v" : state.a.b.c.v + 1
  29. }
  30. }
  31. }
  32. }
  33. }
  34. return state;
  35. }
  36. const store = Redux.createStore(reducer);
  37. const render = ()=>{
  38. document.getElementById("info").innerHTML = store.getState().a.b.c.v;
  39. document.getElementById("info2").innerHTML = store.getState().a.m;
  40. }
  41. render();
  42. store.subscribe(render);
  43.  
  44. document.getElementById("btn").onclick = function(){
  45. store.dispatch({"type" : "ADD"});
  46. }
  47. </script>
  48. </body>

二、Redux和React进行结合开发

在React开发的时候使用Redux可预测状态容器,要装两个新的依赖:

redux:提供createStore、combineReducers、bindActionCreators等功能

react-redux:只提供两个东西,<Provider>组件、connect()函数。

安装两个依赖:

  1. npm install --save redux react-redux

创建reducers文件夹,创建index.js,这个文件暴露一个纯函数:

reducers/index.js:

  1. export default (state = { v : 10}, action) => {
  2. return state;
  3. }

main.js入口文件:

  1. import React from "react";
  2. import ReactDOM from "react-dom";
  3. import {createStore} from "redux";
  4. import App from "./App.js";
  5. import reducer from "./reducers"; //reducer函数
  6.  
  7. //创建 Redux store 来存放应用的状态。
  8. const store = createStore(reducer);
  9.  
  10. ReactDOM.render(
  11. <App></App>,
  12. document.getElementById("app")
  13. );

有两个问题:

如何让组件能够访问到store中的数据?

如果让store中的数据改变的时候,能够自动更新组件的视图

react-redux解决了这两个问题。

在main.js中创建<Provider>组件,它提供的是一个顶层容器的作用,实现store的上下文传递,是让store能够“注入”到所有组件

react-redux提供Provider组件,可以让容器组件拿到state

Provider在根组件外面包一层,这样一来,App所有的子组件就默认都拿到了state了。

原理是React组件的context属性,就是将store这个对象放到上下文(context)中

  1. import React from "react";
  2. import ReactDOM from "react-dom";
  3. import {createStore} from "redux";
  4. import {Provider} from "react-redux";
  5. import App from "./App.js";
  6. import reducer from "./reducers";
  7.  
  8. //创建store仓库
  9. const store = createStore(reducer);
  10.  
  11. ReactDOM.render(
  12. <Provider store={store}>
  13. <App></App>
  14. </Provider>,
  15. document.getElementById('app')
  16. );

组件中要获得全局store的值,需要用connect函数,connect提供连接React组件与Redux store的作用。

  1. connect([mapStateToProps], [mapDispatchToProps])

connect()的第一个参数:mapStateToProps这个函数的第一个参数就是Redux的store,会自动将store的数据作为props绑定到组件上。

connect()的第二个参数:mapDispatchToProps它的功能是,将action作为props绑定到组件上

通俗理解,使用connect可以把state和dispatch绑定到React组件,使得组件可以访问到redux的数据。

常看到下面这种写法:

  1. export default connect()(App)

App.js

  1. import React from "react";
  2. import {connect} from "react-redux";
  3. class App extends React.Component {
  4. constructor() {
  5. super();
  6.  
  7. }
  8. render(){
  9. return <div>
  10. <h1>{this.props.v}</h1>
  11. </div>
  12. }
  13. }
  14. export default connect(
  15. //这个函数return的对象的值,将自动成为组件的props。
  16. (state) => {
  17. return {
  18. v : state.v
  19. }
  20. }
  21. )(App);

一旦connect()(App); 连接某个组件,此时这个组件就会:当全局store发送改变时,如同自己的props发生改变一样,从而进行视图更新。

 

在App.js写两个按钮,可以加1,减1:

  1. import React from "react";
  2. import {connect} from "react-redux";
  3. class App extends React.Component {
  4. constructor() {
  5. super();
  6. }
  7. render(){
  8. return <div>
  9. <h1>{this.props.v}</h1>
  10. <button onClick={()=>{this.props.add()}}>+</button>
  11. <button onClick={()=>{this.props.minus()}}>-</button>
  12. </div>
  13. }
  14. }
  15. export default connect(
  16. (state) => {
  17. return {
  18. v : state.v
  19. }
  20. },
  21. (dispatch) => {
  22. return {
  23. add(){
  24. dispatch({"type" : "ADD"});
  25. },
  26. minus(){
  27. dispatch({"type" : "MINUS"});
  28. }
  29. }
  30. }
  31. )(App);

reducers/index.js提供可预测状态

  1. export default (state = {"v" : 10} , action) => {
  2. if(action.type == "ADD"){
  3. return { "v" : state.v + 1 };
  4. }else if(action.type == "MINUS"){
  5. return { "v" : state.v - 1 };
  6. }
  7. return state;
  8. }

学生管理系统小案例:

reducers/index.js

  1. const initObj = {
  2. "arr" : [
  3. {"id" : 1, "name" : "小明" , "age" : 12},
  4. {"id" : 2, "name" : "小红" , "age" : 13},
  5. {"id" : 3, "name" : "小刚" , "age" : 14}  
  6. ]
  7. }
  8. export default (state = initObj, {type, age, name, id})=>{
  9. if(type == "ADD_STUDENT"){
  10. return {
  11. ...state ,
  12. "arr" : [
  13. ...state.arr ,
  14. {
  15. "id" : state.arr.reduce((a,b)=>{
  16. return b.id > a ? b.id : a;
  17. },0) + 1,
  18. name,
  19. age
  20. }
  21. ]
  22. }
  23. }else if(type == "DEL_STUDENT"){
  24. return {
  25. ...state,
  26. "arr" : state.arr.filter(item=>item.id != id)
  27. }
  28. }
  29. return state;
  30. }

App.js

  1. import React from "react";
  2. import {connect} from "react-redux";
  3. class App extends React.Component {
  4. constructor() {
  5. super();
  6. }
  7. //增加学生
  8. add(){
  9. var name = this.refs.name.value;
  10. var age = this.refs.age.value;
  11. this.props.addStudent(name, age)
  12. }
  13. render(){
  14. return <div>
  15. <p><input type="text" ref="name"/></p>
  16. <p><input type="text" ref="age"/></p>
  17. <button onClick={()=>{this.add()}}>增加学生</button>
  18. {
  19. this.props.arr.map((item,index)=>{
  20. return <p key={item.id}>
  21. {item.id} {item.name} {item.age}岁
  22. <button onClick={()=>{this.props.delStudent(item.id)}}>删除</button>
  23. </p>
  24. })
  25. }
  26. </div>
  27. }
  28. }
  29. export default connect(
  30. //(state) => {
  31. // return { arr : state.arr }
  32. //},
  33. //简化写法
  34. ({arr})=>{
  35. return { arr }
  36. },
  37. (dispatch)=>{
  38. return {
  39. addStudent(name, age){
  40. dispatch({"type" : "ADD_STUDENT", name, age})
  41. },
  42. delStudent(id){
  43. dispatch({"type" : "DEL_STUDENT" , id})
  44. }
  45. }
  46. }
  47. )(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:

  1. const initObj = {
  2. "todos": [
  3. {"id" : 1, "title" : "吃饭", "done" : false},
  4. {"id" : 2, "title" : "睡觉", "done" : false},
  5. {"id" : 3, "title" : "打豆豆","done" : false}
  6. ]
  7. }
  8.  
  9. export default (state = initObj, action) => {
  10. return state;
  11. }

分别创建TodoHd.js、TodoBd.js、TodoFt.js三个组件:

  1. import React from "react";
  2. export default class TodoHd extends React.Component {
  3. constructor() {
  4. super();
  5. }
  6.  
  7. render() {
  8. return <div>
  9. <h1>我是TodoHd组件</h1>
  10. </div>
  11. }
  12. }

示例代码

App.js引入组件

  1. import React from "react";
  2. import {connect} from "react-redux";
  3.  
  4. import TodoHd from "./components/TodoHd.js";
  5. import TodoBd from "./components/TodoBd.js";
  6. import TodoFt from "./components/TodoFt.js";
  7.  
  8. class App extends React.Component {
  9. constructor() {
  10. super();
  11. }
  12.  
  13. render() {
  14. return <div>
  15. <TodoHd></TodoHd>
  16. <TodoBd></TodoBd>
  17. <TodoFt></TodoFt>
  18. </div>
  19. }
  20. }
  21. export default connect()(App)  
  1. import React from 'react';
  2. import {connect} from "react-redux";
  3. import TodoItem from "./TodoItem.js";
  4. class TodoBd extends React.Component {
  5. constructor(props){
  6. super(props);
  7. }
  8. render() {
  9. return (
  10. <div>
  11. //{JSON.stringify(this.props.todos)}
  12. {
  13. this.props.todos.map(item=>{
  14. //return <div key={item.id}>{item.title}</div>
  15. return <TodoItem key={item.id} item={item}></TodoItem>
  16. })
  17. }
  18. </div>
  19. );
  20. }
  21. }  
  22. //connect目的是问“天”要数据,要通天。
  23. //“通天”是比喻,就是说要问store要数据
  24. export default connect(
  25. (state)=>{
  26. return {
  27. todos : state.todos
  28. }
  29. }
  30. )(TodoBd);

TodoItem.js

  1. import React from 'react';
  2. export default class TodoItem extends React.Component {
  3. constructor(props) {
  4. super(props);
  5. }
  6. render() {
  7. return (
  8. <div className="todoItem">
  9. <input type="checkbox" checked={this.props.item.done}/>
  10. <span>{this.props.item.title}</span>
  11. <button>删除</button>
  12. </div>
  13. );
  14. }
  15. }

TodoHd.js增加待办事项

  1. import React from 'react';
  2. import {connect} from "react-redux";
  3. class TodoHd extends React.Component {
  4. constructor(props) {
  5. super(props);
  6. }
  7. render() {
  8. return (
  9. <div>
  10. <input type="text" ref="titleTxt"/>
  11. <button onClick={()=>{
  12. this.props.addTodo(this.refs.titleTxt.value);
  13. this.refs.titleTxt.value = "";
  14. }}>添加</button>
  15. </div>
  16. );
  17. }
  18. }
  19. //这个组件要通天的目的不是要数据,而是改变数据
  20. export default connect(
  21. null ,
  22. (dispatch)=>{
  23. return {
  24. addTodo(title){
  25. dispatch({"type":"ADDTODO", title})
  26. }
  27. }
  28. }
  29. )(TodoHd);

reducers/index.js写可预测状态  

  1. const initObj = {
  2. "todos" : [
  3. ...
  4. ],
  5. }
  6. export default (state = initObj, action)=>{
  7. if(action.type == "ADDTODO"){
  8. return {
  9. ...state,
  10. "todos" : [
  11. ...state.todos,
  12. {
  13. "id" : state.todos.reduce((a,b)=>{
  14. return b.id > a ? b.id : a;
  15. },0) + 1,
  16. "title" : action.title,
  17. "done" : false
  18. }
  19. ]
  20. }
  21. }
  22. return state;
  23. }

TodoFt.js

  1. import React from "react";
  2. import {connect} from "react-redux";
  3. class TodoFt extends React.Component {
  4. constructor() {
  5. super();
  6. }
  7.  
  8. render() {
  9. return <div>
  10. 当前:共{this.props.todos.length}条信息--
  11. 已做{this.props.todos.filter(item => item.done).length}条--
  12. 未做{this.props.todos.filter(item => !item.done).length}条
  13. </div>
  14. }
  15. }
  16.  
  17. export default connect(
  18. (state)=>{
  19. return {
  20. todos:state.todos
  21. }
  22. }
  23. )(TodoFt)

示例代码

因为TodoItem这个组件是不通天的,所以TodoItem是不能自己独立dispatch到store的。

此时就需要TodoBd帮助,因为TodoBd是通天。

这是套路:所有被循环语句map出来的组件,一律不通天,数据父亲给,改变store的能力父亲给。

TodoBd.js组件引入了TodoItem.js组件,因为TodoItem组件是被map出来的,所以信息要传给每一个TodoItem,而不是让TodoItem自己通天拿数据。

TodoBd.js

  1. import React from 'react';
  2. import {connect} from "react-redux";
  3. import TodoItem from "./TodoItem.js";
  4.  
  5. class TodoBd extends React.Component {
  6. constructor(props) {
  7. super(props);
  8. }
  9. render() {
  10. //根据全局的show属性来决定当前todos数组
  11. if(this.props.show == "ALL"){
  12. var todos = this.props.todos;
  13. }else if(this.props.show == "ONLYDONE"){
  14. var todos = this.props.todos.filter(item=>item.done);
  15. }else if(this.props.show == "ONLYUNDONE"){
  16. var todos = this.props.todos.filter(item=>!item.done);
  17. }
  18.  
  19. return (
  20. <div>
  21. {
  22. todos.map(item=>{
  23. return <TodoItem
  24. key={item.id}
  25. item={item}
  26. delTodo={this.props.delTodo.bind(this)}
  27. changeTodo={this.props.changeTodo.bind(this)}
  28. ></TodoItem>
  29. })
  30. }
  31. </div>
  32. );
  33. }
  34. }
  35. export default connect(
  36. (state)=>{
  37. return {
  38. todos : state.todos ,
  39. show : state.show
  40. }
  41. },
  42. (dispatch)=>{
  43. return {
  44. delTodo(id){
  45. dispatch({"type" : "DELTODO", id});
  46. },
  47. changeTodo(id , k , v){
  48. dispatch({"type" : "CHANGETODO", id, k, v});
  49. }
  50. }
  51. }
  52. )(TodoBd);

TodoItem.js

  1. import React from 'react';
  2. export default class TodoItem extends React.Component {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. "onEdit" : false
  7. }
  8. }
  9. render() {
  10. const {id, title, done} = this.props.item;
  11. return (
  12. <div>
  13. <input
  14. type="checkbox" checked={done}
  15. onChange={(e)=>{
  16.               this.props.changeTodo(id, "done", e.target.checked)
  17.             }}
  18. />
  19. {
  20. this.state.onEdit
  21. ?
  22. <input
  23. type="text"
  24. defaultValue={title}
  25. onBlur={(e)=>{
  26. this.props.changeTodo(id,"title", e.target.value)
  27. this.setState({"onEdit" : false})
  28. }}
  29. />
  30. :
  31. <span onDoubleClick={()=>{this.setState({"onEdit":true})}}>
  32.             {title}
  33.           </span>
  34. }
  35.  
  36. <button onClick={()=>{this.props.delTodo(id)}}>删除</button>
  37. </div>
  38. );
  39. }
  40. }

index.js

  1. const initObj = {
  2. "todos" : [
  3. ...
  4. ],
  5. "show" : "ALL" //ALL、ONLYDONE、ONLYUNDONE
  6. }
  7. export default (state = initObj, action) => {
  8. if(action.type == "ADDTODO"){
  9. ...
  10. }else if(action.type == "DELTODO"){
  11. return {
  12. ...state,
  13. "todos" : state.todos.filter(item => item.id != action.id)
  14. }
  15. }else if(action.type == "CHANGETODO"){
  16. return {
  17. ...state,
  18. "todos" : state.todos.map(item => {
  19. //如果遍历到的item项和传入的aciton的id项不一样,此时返回原item
  20. if(item.id != action.id) return item;
  21. //否则返回修改之后的item
  22. return {
  23. ...item ,
  24. [action.k] : action.v
  25. }
  26. })
  27. }
  28. }else if(action.type == "CHANGESHOW"){
  29. return {
  30. ...state,
  31. "show" : action.show
  32. }
  33. }
  34. return state;
  35. }

TodoFt.js

  1. import React from 'react';
  2. import {connect} from "react-redux";
  3. import classnames from "classnames";
  4.  
  5. class TodoFt extends React.Component {
  6. constructor(props) {
  7. super(props);
  8. }
  9.  
  10. render() {
  11. return (
  12. <div>
  13. <p>
  14. 当前共{this.props.todos.length}条信息
  15. 做完{this.props.todos.filter(item=>item.done).length}条
  16. 未做{this.props.todos.filter(item=>!item.done).length}条
  17. </p>
  18. <p>
  19. <button className={classnames({"cur":this.props.show == 'ALL'})}
                onClick={()=>{this.props.changeShow('ALL')}}>查看全部
  20.             </button>
  21. <button className={classnames({"cur":this.props.show == 'ONLYDONE'})}
  22. onClick={()=>{this.props.changeShow('ONLYDONE')}}>仅看已做
  23. </button>
  24. <button className={classnames({"cur":this.props.show == 'ONLYUNDONE'})}
  25. onClick={()=>{this.props.changeShow('ONLYUNDONE')}}>仅看未做
  26. </button>
  27. </p>
  28. </div>
  29. );
  30. }
  31. }
  32.  
  33. export default connect(
  34. (state) => {
  35. return {
  36. todos : state.todos ,
  37. show : state.show
  38. }
  39. },
  40. (dispatch) => {
  41. return {
  42. changeShow(show){
  43. dispatch({"type" : "CHANGESHOW" , show})
  44. }
  45. }
  46. }
  47. )(TodoFt);

四、logger插件

redux-logger用来辅助开发。

  1. npm install --save redux-logger

改变main.js:

  1. import React from "react";
  2. import ReactDOM from "react-dom";
  3. import {createStore , applyMiddleware} from "redux";
  4. import {Provider} from "react-redux";
  5. import logger from "redux-logger";
  6.  
  7. import App from "./App.js";
  8. //引入reducer
  9. import reducer from "./reducers/index.js";
  10.  
  11. //创建store
  12. const store = createStore(reducer , applyMiddleware(logger));
  13.  
  14. ReactDOM.render(
  15. <Provider store={store}>
  16. <App></App>
  17. </Provider>
  18. ,
  19. document.getElementById("app-container")
  20. );

也可以使用redux-devtools这个插件。

  1. npm install --save-dev redux-devtools
  2. npm install --save-dev redux-devtools-log-monitor
  3. npm install --save-dev redux-devtools-dock-monitor
  4. npm install --save-dev redux-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.1 combineReducers

reducers/counter.js就是一个普通的纯函数:

  1. export default (state = {"v" : 10},action)=>{
  2. return state;
  3. }

reducers/todo.js提供的数据:

  1. const initObj = {
  2. "todos": [
  3. { "id": 1, "title": "吃饭", "done": false },
  4. { "id": 2, "title": "睡觉", "done": false },
  5. { "id": 3, "title": "打豆豆", "done": true }
  6. ]
  7. };
  8.  
  9. export default (state = initObj, action) => {
  10. return state
  11. }

示例代码

reducers/index.js要用redux提供的combineReducers来进行智能合并

  1. import { combineReducers } from "redux";
  2. import counter from "./counter.js";
  3. import todos from "./todos.js";
  4.  
  5. //暴露合并的reducer
  6. export default combineReducers({
  7. counter,
  8. todos
  9. })

main.js

  1. import React from "react";
  2. import ReactDOM from "react-dom";
  3. import {createStore} from "redux";
  4. import {Provider} from "react-redux";
  5.  
  6. import App from "./containers/App.js";
  7. //引入reducer
  8. import reducer from "./reducers";
  9.  
  10. // 创建 Redux store 来存放应用的状态。
  11. const store = createStore(reducer);
  12.  
  13. ReactDOM.render(
  14. <Provider store={store}>
  15. <App></App>
  16. </Provider>,
  17. document.getElementById("app")
  18. );

containers/App.js组件使用数据

  1. import React from 'react';
  2. import {connect} from "react-redux";
  3. class App extends React.Component {
  4. constructor(){
  5. super();
  6. }
  7. render() {
  8. return (
  9. <div>
  10. <h1>{this.props.v}</h1>
  11. </div>
  12. );
  13. }
  14. }
  15. export default connect(
  16. ({counter}) => ({
  17. v : counter.v
  18. })
  19. )(App);

components/TodoList/index.js组件

  1. import React from "react";
  2. import { connect } from "react-redux";
  3. class TodoList extends React.Component {
  4. constructor(){
  5. super();
  6. }
  7. render() {
  8. return (
  9. <div>
  10. <h1>我是TodoList</h1>
  11. {
  12. this.props.todos.map(item=>{
  13. return <p key={item.id}>{item.title}</p>
  14. })
  15. }
  16. </div>
  17. )
  18. }
  19. };
  20. export default connect(
  21. ({todos: {todos}})=>{
  22. return {
  23. todos
  24. }
  25. }
  26. )(TodoList)

containers/App.js引入组件:

  1. import React from 'react';
  2. import TodoList from "../components/todolist/index.js";
  3. import Counter from "../components/counter/index.js";
  4.  
  5. export default class App extends React.Component {
  6. constructor(props) {
  7. super(props);
  8. }
  9. render() {
  10. return (
  11. <div>
  12. <Counter></Counter>
  13. <TodoList></TodoList>
  14. </div>
  15. );
  16. }
  17. }

5.2 bindActionCreators

bindActionCreators会将action和dispatch绑定并返回一个对象,这个对象会作为props的一部分传入组件中。

bindActionCreators主要作用:一般情况下,可以通过Provider将store通过React的connext属性向下传递,bindActionCreators的唯一用处就是需要传递action creater到子组件,并且该子组件并没有接收到父组件上传递的store和dispatch。

官方的文件夹结构:https://github.com/reduxjs/redux/tree/master/examples/todomvc/src

actions/counterActions.js:新建actions文件夹存放type

  1. // 我们把return一个action的函数叫做“action creator”
  2. // 所以这个文件向外暴露了几个动作
  3. export const add = () => ({ type: "ADD" })
  4. export const minus = () => ({ type: "MINUS" })
  5. export const cheng = () => ({ type: "CHENG" })
  6. export const chu = () => ({ type: "CHU" })

counter/index.js计数器组件

  1. import React from 'react';
  2. import {bindActionCreators} from "redux";
  3. import {connect} from "react-redux";
  4. import * as counterActions from "../../actions/counterActions.js";
  5.  
  6. class Counter extends React.Component {
  7. constructor(props) {
  8. super(props);
  9. }
  10. render() {
  11. return (
  12. <div>
  13. <h1>Counter : {this.props.v}</h1>
  14. <button onClick={()=>{this.props.counterActions.add()}}>加</button>
  15. <button onClick={()=>{this.props.counterActions.minus()}}>减</button>
  16. <button onClick={()=>{this.props.counterActions.cheng()}}>乘</button>
  17. <button onClick={()=>{this.props.counterActions.chu()}}>除</button>
  18. </div>
  19. );
  20. }
  21. }
  22. export default connect(
  23. ({counter}) => ({
  24. v : counter.v
  25. }),
  26. (dispatch) => ({
  27. //这里的dispatch,等同于store中的store.dispatch,用于组合action
  28. counterActions : bindActionCreators(counterActions , dispatch)
  29. })
  30. )(Counter);

app/reducers/counter.js:

  1. import {ADD, MINUS, CHENG, CHU} from "../constants/COUNTER.js";
  2.  
  3. export default (state = {"v" : 0} , action) => {
  4. if(action.type == "ADD"){
  5. return {
  6. ...state ,
  7. "v" : state.v + 1
  8. }
  9. }else if(action.type == "MINUS"){
  10. return {
  11. ...state ,
  12. "v" : state.v - 1
  13. }
  14. }else if(action.type == "CHENG"){
  15. return {
  16. ...state ,
  17. "v" : state.v * 2
  18. }
  19. }else if(action.type == "CHU"){
  20. return {
  21. ...state ,
  22. "v" : state.v / 2
  23. }
  24. }
  25. return state;
  26. }

示例代码

todolist/index.js

  1. import React from 'react';
  2. import TodoHd from "./TodoHd.js";
  3. import TodoBd from "./TodoBd.js";
  4.  
  5. export default class TodoList extends React.Component {
  6. constructor(props) {
  7. super(props);
  8. }
  9. render() {
  10. return (
  11. <div>
  12. <h1>TodoList</h1>
  13. <TodoHd></TodoHd>
  14. <TodoBd></TodoBd>
  15. </div>
  16. );
  17. }
  18. }

TodoHd.js

  1. import React from 'react';
  2. import {bindActionCreators} from "redux";
  3. import {connect} from "react-redux";
  4. import * as todoActions from "../../actions/todoActions.js";
  5.  
  6. class TodoHd extends React.Component {
  7. constructor(props) {
  8. super(props);
  9. }
  10. render() {
  11. return (
  12. <div>
  13. <input type="text" ref="titleTxt"/>
  14. <button onClick={()=>{this.props.todoActions.add(this.refs.titleTxt.value)}}
  15.             >添加
  16.           </button>
  17. </div>
  18. );
  19. }
  20. }
  21. export default connect(
  22. null ,
  23. (dispatch) => ({
  24. todoActions : bindActionCreators(todoActions , dispatch)
  25. })
  26. )(TodoHd);

TodoBd.js

  1. import React from 'react';
  2. import {bindActionCreators} from "redux";
  3. import {connect} from "react-redux";
  4. import * as todoActions from "../../actions/todoActions.js";
  5.  
  6. class TodoBd extends React.Component {
  7. constructor(props) {
  8. super(props);
  9. }
  10. render() {
  11. return (
  12. <div>
  13. {
  14. this.props.todos.map(item=>{
  15. return <p key={item.id}>
  16. {item.title}
  17. <button onClick={()=>{this.props.todoActions.del(item.id)}}>
  18.                  删除
  19.                   </button>
  20. </p>
  21. })
  22. }
  23. </div>
  24. );
  25. }
  26. }
  27.  
  28. export default connect(
  29. ({todo}) => ({
  30. todos : todo.todos
  31. }) ,
  32. (dispatch) => ({
  33. todoActions : bindActionCreators(todoActions , dispatch)
  34. })
  35. )(TodoBd);

为了防止action的type命名冲突,此时要单独存放在const文件夹中:

app\constants\COUNTER.js

  1. export const ADD = "ADD_COUNTER";
  2. export const MINUS = "MINUS_COUNTER";
  3. export const CHENG = "CHENG_COUNTER";
  4. export const CHU = "CHU_COUNTER";

app\constants\TODO.js

  1. export const ADD = "ADD_TODO";
  2. export const DEL = "DEL_TODO";

然后就可以在以下文件中,引入以上常量,然后使用大写的常量替换type字符串

l actions中的counterActions.js和todoActions.js

l reducers中的todo.js和counter.js

actions/TodoActions.js

  1. import {ADD , DEL} from "../constants/TODO.js";
  2. export const add = (title) => ({"type" : ADD, title});
  3. export const del = (id) => ({"type" : DEL, id});

actions/counterActions.js:

  1. import {ADD , MINUS , CHENG , CHU} from "../constants/COUNTER.js";
  2. export const add = () => ({"type" : ADD});
  3. export const minus = () => ({"type" : MINUS});
  4. export const cheng = () => ({"type" : CHENG});
  5. export const chu = (n) => ({"type" : CHU , n});

reducers/todo.js

  1. import {ADD , DEL} from "../constants/TODO.js";
  2. const initObj = {
  3. "todos" : [
  4. {"id" : 1 , "title" : "吃饭" , "done" : false},
  5. {"id" : 2 , "title" : "睡觉" , "done" : false},
  6. {"id" : 3 , "title" : "打豆豆" , "done" : false}
  7. ]
  8. }
  9. export default (state = initObj , action) => {
  10. if(action.type == ADD){
  11. return {
  12. ...state ,
  13. "todos" : [
  14. ...state.todos ,
  15. {
  16. "id" : state.todos.reduce((a,b)=>{return b.id > a ? b.id : a},0) + 1,
  17. "title": action.title,
  18. "done" : action.done
  19. }
  20. ]
  21. }
  22. }else if(action.type == DEL){
  23. return {
  24. ...state ,
  25. "todos" : state.todos.filter(item => item.id != action.id)
  26. }
  27. }
  28. return state;
  29. }

reducers/counter.js

  1. import {ADD , MINUS , CHENG , CHU} from "../constants/COUNTER.js";
  2.  
  3. export default (state = {"v" : 0} , action) => {
  4. if(action.type == ADD){
  5. return {
  6. ...state ,
  7. "v" : state.v + 1
  8. }
  9. }else if(action.type == MINUS){
  10. return {
  11. ...state ,
  12. "v" : state.v - 1
  13. }
  14. }else if(action.type == CHENG){
  15. return {
  16. ...state ,
  17. "v" : state.v * 2
  18. }
  19. }else if(action.type == CHU){
  20. return {
  21. ...state ,
  22. "v" : state.v / action.n
  23. }
  24. }
  25. return state;
  26. }

前端笔记之React(五)Redux深入浅出的更多相关文章

  1. 读书笔记之第五回深入浅出关键字---把new说透

    第五回深入浅出关键字---把new说透  ------你必须知道的.net读书笔记 new一个class时,new完成了以下两个方面的内容:一是调用newobj命令来为实例在托管堆中分配内存:二是调用 ...

  2. 前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发

    一.React生命周期 一个组件从出生到消亡,在各个阶段React提供给我们调用的接口,就是生命周期. 生命周期这个东西,必须有项目,才知道他们干嘛的. 1.1 Mouting阶段[装载过程] 这个阶 ...

  3. 前端笔记之React(六)ES6的Set和Map&immutable和Ramda和lodash&redux-thunk

    一.ES6的Set.Map数据结构 Map.Set都是ES6新的数据结构,都是新的内置构造函数,也就是说typeof的结果,多了两个: Set 是不能重复的数组 Map 是可以任何东西当做键的对象 E ...

  4. 前端笔记之React(八)上传&图片裁切

    一.上传 formidable天生可以处理上传的文件,非常简单就能持久上传的文件. 今天主要讲解的是,前后端的配合套路. 上传分为同步.异步.同步公司使用非常多,异步我们也会讲解. 1.1 先看一下a ...

  5. 前端笔记之React(七)redux-saga&Dva&路由

    一.redux-saga解决异步 redux-thunk 和 redux-saga 使用redux它们是必选的,二选一,它们两个都可以很好的实现一些复杂情况下redux,本质都是为了解决异步actio ...

  6. 前端笔记之React(一)初识React&组件&JSX语法

    一.React项目起步配置 官网:https://reactjs.org/ 文档:https://reactjs.org/docs/hello-world.html 中文:http://react.c ...

  7. 前端笔记:React的form表单全部置空或者某个操作框置空的做法

    1.全部置空的做法,一般在弹出框关闭后,需要重置该form所有表单: this.props.form.resetFields(); 2.针对某个操作框置空的做法 例如,form表单里有一个部门和一个张 ...

  8. 前端笔记之React(二)组件内部State&React实战&表单元素的受控

    一.组件内部的State 1.1 state state叫状态,是每一个类式组件都有的属性,但函数式组件,没有state. state是一个对象,什么值都可以定义. 在任何类式组件的构造函数中,可以用 ...

  9. 前端笔记之React(三)使用动态样式表&antd&React脚手架&props实战

    一.使用动态样式表 1.1 LESS使用 全局安装Less npm install -g less 创建1.less文件,然后可以用lessc命令来编译这个文件: lessc 1.less 1.css ...

随机推荐

  1. jdk9新特性之jShell

    jdk9还没研究完,结果jdk10都停止维护了. 最近回顾jdk9,发现了一个新特性--jShell. jdk9是在2017年的9月份发布的,这是我开始感觉入门java的时间.从jdk10开始就是半年 ...

  2. ThinkPHP判断post,get操作

    define('REQUEST_METHOD',$_SERVER['REQUEST_METHOD']); define('IS_GET', REQUEST_METHOD =='GET' ? true ...

  3. Java学习笔记——Linux下安装配置tomcat

    朝辞白帝彩云间,千里江陵一日还. 两岸猿声啼不住,轻舟已过万重山. ——早发白帝城 首先需要安装配置JDK,这里简单回顾下.Linux下用root身份在/opt/文件夹下创建jvm文件夹,然后使用ta ...

  4. PATB 1028. 人口普查(20)

    1028. 人口普查(20) 注意特判合理人数为0,否则格式错误.很暴力的sort排序找出最大最小. 时间限制 200 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Stan ...

  5. SCIgen与野鸡期刊的梗

    layout: post title: "SCIgen与野鸡期刊的梗" date: 2019-04-28 19:06:21 +0800 --- 作者:吴甜甜 个人博客网站: wut ...

  6. 关于火狐浏览器设置cookie的一个问题

    最近发现我一个项目的网页,里面的cookie无法添加了,急的我瞪着我的PHP代码沉思了好久,我默认用的火狐浏览器,然而我默默的打开另一个叫360的浏览器,发现它的cookie是正常添加的. ... 难 ...

  7. 数据库root密码删除

    1 打开mysql.exe和mysqld.exe所在的文件夹,复制路径地址   2 打开cmd命令提示符,进入上一步mysql.exe所在的文件夹.   3 输入命令  mysqld --skip-g ...

  8. myecliese加大内存

    加大内存代码 : -Xms512m -Xmx1024m -XX:PermSize=256M -XX:MaxPermSize=1024m

  9. wcf服务编程(一)

    步骤一:定义契约 [ServiceContract] //定义服务契约 需要引用System.ServiceModel public interface ICalculator { [Operatio ...

  10. 自定义HashSet判重标准

    HashSet在执行add时会首先根据目标类的hashcode判断是否有与其hashcode相同的对象,若有则使用equals该对象判断是否与其相同. HashSet保证了元素的唯一性, 我们可以通过 ...