什么情况下用Redux明确一点，Redux是一个有用的框架，但不是非用不可。事实上，大多数情况下，你可以不用他，只用React就够了。用户的使用方式非常简单用户之间没有协作不需要与服务器大量交互，也没有使用 WebSocket视图层（View）只从单一来源获取数据上面这些情况，都不需要使用Redux用户的使用方式复杂不同身份的用户有不同的使用方式（比如普通用户和管理员）多个用户之间可以协作与服务器大量交互，或者使用了WebSocketView要从多个来源获取数据上面这些情况才是Redux的适用场景：多交互，多数据源从组件的角度看，如果你的应用有以下场景，可以考虑使用Redux某个组件的状态，需要共享某个状态需要在任何地方都可以拿到一个组件需要改变全局状态一个组件需要改变另一个组件的状态总之，不要把Redux当做万灵丹，如果你的应用没那么复杂就没必要用它。Redux的三个基本原则单一数据源整个应用的state被储存在一棵object tree，并且这个object tree只存在于唯一的store中State是只读的唯一改变state的方法就是触发action，action是一个用于描述已经发生事件的普遍对象。使用纯函数来执行修改为了描述action如何改变state tree，你需要并编写reducers基本概念和APIStorestore就是保存数据的地方，你可以把它看成容器，整个应用只能有一个Store。 import { createStore } from 'redux'; const store = createStore(fn); Redux提供createStore这个函数，用来生成StoreStateStore对象包含所有数据。如果想得到某个时点的数据，就要对Store生成快照。 这种时点的数据的集合，就叫做State。当前时刻的State，可以通过 store.getState()拿到。 import { createStore } from 'redux'; const store = createStore(fn); const state = store.getState();Redux规定，一个State对应一个View。只要State相同，view就相同。ActionState的变化会导致View的变化，但是，用户接触不到State，只能接触到View。 所以，State的变化必须是View导致的。Action就是View发出的通知，表示State应 该要变化了。const action = { type: 'ADD_TODO', payload: 'Learn Redux' };Action是一个对象。其中的type属性是必须的，表示Action的名称。其他属性可 以自由设置。Action描述当前发生的事情。改变State的唯一办法，就是使用Action。它会运送 数据到Store。Action CreatorView 要发送多少种消息，就会有多少种 Action。如果都手写，会很麻烦。可以定义一个函数来生成 Action，这个函数就叫 Action Creator。const ADD_TODO = '添加 TODO'; function addTodo(text) { return { type: ADD_TODO, text } } const action = addTodo('Learn Redux');上述代码中，addTodo函数就是一个Action Creatorstore.dispatch() store.dispatch()是View发出Action的唯一方法 import { createStore } from 'redux'; const store = createStore(fn); store.dispatch({ type: 'ADD_TODO', payload: 'Learn Redux' }); store.dispatch()接受一个Action对象作为参数，将他发送出去Reducerstore收到Action以后，必须给出一个State，这样View才会发生变化。这种State的计算过程就叫做Reducer。Reducer是一个函数，他接受Action和当前State作为参数，返回一个新的Stateconst reducer=function (state,Action){ //.... return new_state; }整个应用的初始状态，可以作为State的默认值。const defaultState=0; const reducer=(state=defaultState,action)=>{ switch (action.type){ case "ADD": return state + action.payload; default: return state; } } const state=reducer(1,{ type:"ADD", payload:2 }); 上面代码中，reducer函数收到名为ADD的Action以后，就返回一个新的State，作为加法的计算结果。实际应用中，Reducer函数是不用像上面这样手动调用的，store.dispath方法会触发Reducer的自动执行。为此Store需要知道Reducer函数，做法就是在生成Store的时Reducer传入createrStore方法。import { createStore } from 'redux' const stor = createStore(reducer)上面代码中，createStore接受Reducer作为参数，生成一个新的Store。以后每当store.dispath发过来一个新的Action，就会自定调用Reducer，得到新的State。Reducer还可以作为数组的reduce方法(reduce方法是使数组累加)的参数。const actions = [ { type: 'ADD', payload: 0 }, { type: 'ADD', payload: 1 }, { type: 'ADD', payload: 2 } ]; const total = actions.reduce(reducer, 0); // 3上面代码中，数组action表示一次有三个Action，分别是0，加1和加2。数组的reudce方法接受Reducer函数作为参数，就可以得到最终的状态。纯函数Reducer函数最重要的特征是，他是一个纯函数，也就是说，只要是同样的输入，必得到同样的输出。纯函数是函数是编程的概念，必须遵守以下一些约束 - 不得改写参数 - 不能调用系统 I/O 的API - 不能调用`Date.now()`或者`Math.random()`等不纯的方法，因为每次会得到不一样的结果 由于Reducer是纯函数，就可以保证同样的State，必定得到同样的View。但也正是因为这一点，Reducer函数里面不能改变State，必须返回一个全新的对象//State是一个对象 function reducer(state, action) { return Object.assign({}, state, { thingToChange }); // 或者 return { ...state, ...newState }; } // State 是一个数组 function reducer(state, action) { return [...state, newItem]; }最好把State对象设成只读。你没法改变它，要得到新的State，唯一办法就是生成一个新对象。这样的好处是，任何时候，与某个View对应的State总是一个不变的对象。store.subscribe()Store允许store.subscribe方法设置监听函数，一旦State发生变化，就自动执着这个函数。import { createStore } from 'redux' const store=createStore(reducer); store.subscribe(lister);显然，只要把View的更新函数(对于React项目，就是组件的render方法或setState方法)放入listen，就会实现View的自动渲染。store.subscrible方法返回一个函数，调用这个函数就可以解除监控。let unsubscrible=store.subscrible(()=> console.log(store.getState()); );Store的实现Store提供了三个方法 - store.getState()//获得当前State - store.dispatch()//从view发出Action的唯一方法 - store.subsrcible()//一旦State发生变化，就调用()里的函数 下面是createStore方法的一个简单实现，可以了解一下Store是怎么生成的。const createStore=(reducer)=>{ let state let listeners=[] const getState=()=>state const dispatch=(action)=>{ state=reducer(state,action) listeners.forEach(listener=>listener()) } const subscribe=(listener)=>{ listrners.push(listener) return()=>{ listeners=listeners.filter(l=>l !==listener) } } dispatch({}) return {getState,dispatch,subscribe} }上面代码很生动的写明白了，store的三个方法的生成过程，以及为啥调用dispatch的时候会自动调用reducer函数。工作流程首先，用户发出Actionstore.dispatch(action) 然后Store会自动调用Reducer，并且传入两个参数，当前State和收到的Action。Reducer会返回一个新的Statelet nextState=todoApp(State,action)State一旦有变化，Store就会调用监听函数。store.subscribe(listener)listener可以通过store.getState()得到当前状态。如果使用的是React，这时可以出发重新渲染View。function listener(){ let newState=store.getState() component.setState({ newState }) }
前言index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import TodoList from './TodoList'; ReactDOM.render(<TodoList />, document.getElementById('root'));store/index.js// 引入redux中的createStore方法 import { createStore } from 'redux'; // 将管理员的小本本（reducer）引入过来让其与管理员配对，这个小本本就是属于这个管理员的 import reducer from './reducer' // 定义一个store去管理这个小本本 const store = createStore(reducer); export default store;store/reducer.jsconst defaultState = { inputValue:'', list: [] } // reducer能够接收state，但是不能够修改state export default (state = defaultState ,action) => { // state:存储数据 // action:用户传过来的那句话 // 这里我们能够获得了TodoList.js传过来的信息，接下来我们拿到这些信息进行处理判断 if(action.type === 'change_input_value') { // 深拷贝，我们想要将接收到的value实时的显示在页面上，我们就必须修改defaultState中的数据，但是reducer又绝不能进行修改state，所以我们进行深拷贝，修改拷贝后的内容。 const newState = JSON.parse(JSON.stringify(state)); newState.inputValue = action.value; return newState; } if(action.type === 'add_todo_item') { const newState = JSON.parse(JSON.stringify(state)); newState.list.push(newState.inputValue); newState.inputValue = ''; return newState; } if(action.type === 'delete_todo_item') { const newState = JSON.parse(JSON.stringify(state)); newState.list.splice(action.index,1); return newState; } return state; }TodoList.jsimport React, { Component } from 'react' import 'antd/dist/antd.css' import { Input, Button, List } from 'antd'; // 引入store,进行redux与react之间的数据信息交互 import store from './store/index.js'; class TodoList extends Component { constructor(props){ super(props); this.state = store.getState() this.handleInputChange = this.handleInputChange.bind(this) this.handleStoreChange = this.handleStoreChange.bind(this) this.handleBtnClick = this.handleBtnClick.bind(this) this.handleItemDelete = this.handleItemDelete.bind(this) // subscribe订阅store传来的数据，只要store数据改变，那么subscribe中的函数就会改变 store.subscribe(this.handleStoreChange); } render() { return ( 提交 <List.Item onClick={this.handleItemDelete.bind(this,index)}>{item}</List.Item>} /> ) } // 执行input框内容 handleInputChange(e){ // 创建一个action用于执行输入框输入这一动作，这个动作包括用户的所要执行的动作信息和传输的数据，type：定义的动作类型，value：传输的数据 const action = { type:'change_input_value', value:e.target.value } // 调用dispath方法将action传递给store store.dispatch(action); } // 数据的同步更新 handleStoreChange() { // 同步更新数据 this.setState(store.getState()) } // 添加功能 handleBtnClick() { const action = { type:'add_todo_item', }; store.dispatch(action); } // 删除功能，index（上面传过来的） handleItemDelete(index) { const action = { type:'delete_todo_item', index } store.dispatch(action); } } export default TodoList;
使用Redux编写简单的TodoList创建项目我使用的是create-react-app安装的TodoList项目安装相关依赖cnpm i antd redux创建如图目录代码及相关解释index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import TodoList from './TodoList'; ReactDOM.render(<TodoList />, document.getElementById('root'));TodoList.jsimport React, { Component } from 'react' import 'antd/dist/antd.css' import { Input, Button, List } from 'antd'; // 引入store,进行redux与react之间的数据信息交互 import store from './store/index.js'; class TodoList extends Component { constructor(props){ super(props); this.state = store.getState() this.handleInputChange = this.handleInputChange.bind(this) this.handleStoreChange = this.handleStoreChange.bind(this) this.handleBtnClick = this.handleBtnClick.bind(this) this.handleItemDelete = this.handleItemDelete.bind(this) // subscribe订阅store传来的数据 store.subscribe(this.handleStoreChange); } render() { return ( 提交 <List.Item onClick={this.handleItemDelete.bind(this,index)}>{item}</List.Item>} /> ) } // 执行input框内容 handleInputChange(e){ // 创建一个action用于执行输入框输入这一动作，这个动作包括用户的所要执行的动作信息和传输的数据，type：定义的动作类型，value：传输的数据 const action = { type:'change_input_value', value:e.target.value } // 调用dispath方法将action传递给store store.dispatch(action); } // 数据的同步更新 handleStoreChange() { // 同步更新数据 this.setState(store.getState()) } // 添加功能 handleBtnClick() { const action = { type:'add_todo_item', }; store.dispatch(action); } // 删除功能，index（上面传过来的） handleItemDelete(index) { const action = { type:'delete_todo_item', index } store.dispatch(action); } } export default TodoList;store/index.js// 引入redux中的createStore方法 import { createStore } from 'redux'; // 将管理员的小本本（reducer）引入过来让其与管理员配对，这个小本本就是属于这个管理员的 import reducer from './reducer' // 定义一个store去管理这个小本本 const store = createStore(reducer); export default store;store/reducer.jsconst defaultState = { inputValue:'', list: [] } // reducer能够接收state，但是不能够修改state export default (state = defaultState ,action) => { // state:存储数据 // action:用户传过来的那句话 // 这里我们能够获得了TodoList.js传过来的信息，接下来我们拿到这些信息进行处理判断 if(action.type === 'change_input_value') { // 深拷贝，我们想要将接收到的value实时的显示在页面上，我们就必须修改defaultState中的数据，但是reducer又绝不能进行修改state，所以我们进行深拷贝，修改拷贝后的内容。 const newState = JSON.parse(JSON.stringify(state)); newState.inputValue = action.value; return newState; } if(action.type === 'add_todo_item') { const newState = JSON.parse(JSON.stringify(state)); newState.list.push(newState.inputValue); newState.inputValue = ''; return newState; } if(action.type === 'delete_todo_item') { const newState = JSON.parse(JSON.stringify(state)); newState.list.splice(action.index,1); return newState; } return state; }使用redux实现简单的todolist功能
在将redux之前我们先回顾一下react的数据流传递方式，因为react是V层框架，所以它需要数据流来进行支撑。从上图可以看出，H和E两个组件状态、信息可以直接进行传递。B和E、A和B也可以直接进行组件状态、信息的传递。但是H里的状态想要传给B就必须通过E来进行传送。A里面的数据想要给H传送就必须通过B、E两个组件。属性this.props用于父子组件之间传递属性；属性this.state用于表示组件本身的状态，当state变化时，该组件会根据最新的state正确渲染出来。组件少的话，还可以进行使用，当组件数量非常庞大的时候，维护起来是相当的困难。随着单页面应用的普及，web app内部需要管理的状态越来越多，这些状态可能来自服务器端，用户输入的数据，用户交互数据，当前UI状态，本地的缓存数据等等。如何能够有条理的管理这些数据，成为前端开发中一个难题。这就需要我们的redux来解决这个问题了。