React本身是一个轻量级的视图框架,如果只用react框架,做大型项目的时候,组件间的传值将会变得非常麻烦。这个时候,我们需要用引入数据管理框架Redux, Redux就是把组件中的数据,放到一个公用的存储区域去存储,然后当一个组件改变store里的数据的时候,其他组件会感知到store里数据的变化,然后重新去取store里的最新数据。Redux大大简化了数据的传递难度。
image.pngRedux = Reducer + Flux
Redux的工作流程
简单理解为以下流程:组件(React Components)
告诉Store
,我需要获取你里边的数据(Action Creators)
,但是到底该给组件什么数据呢?Store并不知道, 这个需要Reducers
告诉Store应该给什么数据,Store知道了以后,把数据给组件就可以了。
同样道理,当组件要修改Store里的数据
的时候,需要先告诉Store,我要改变你里边的数据(Action Creators)
,但是Store不知道怎么去帮你改变里边的数据,它需要去问 Reducers,Reducers会告诉Store该怎么去修改数据,当Store改好数据以后,会通知组件,数据已经修改好了,你可以重新来获取数据了。这样组件就可以重新获取最新的数据了!
redux使用流程
- 安装redux
npm install --save redux
- 创建一个store,具体方法是,src下,创建一个store文件夹,里边创建一个index.js文件,然后index.js里边引入redux里的
createStore方法
,当然我们不能单单创建一个store,还需要在创建store的时候,传入一个reducer
,reducer返回的必须是一个函数,这个函数接收两个参数,state和action,reducer负责管理整个应用里的一些数据,怎么处理,怎么存。
image.png
image.png
第一次reducer第一次执行的时候,store传的state是没有数据的,所以准备了一个defaultState作为默认值,复制给state,然后再把这个state返回给store,所以defaultState实际上,就是store的默认值。
具体使用流程,以todolist为例
- 首先我们要实现input框输入的时候,store里的inputValue值跟着改变的功能。然后把store里的inputValue值放入store的list.
// 第一步:input框上,要监听input的change事件,并绑定事件处理函数。
<Input
value={this.state.inputValue}
placeholder="todo info"
style={{width:'300px',marginRight:'10px'}}
onChange={this.handleInputChange}
>
</Input>
// 第二步:当change事件触发的时候,创建一个action,action是一个对象。
// action包含type,要做什么事(自定义名字),和要传的数据。
// 然后再调用store的dispatch方法,把这个action传给store
handleInputChange(e){
const action = {
type:'change_input_value',
value:e.target.value
}
store.dispatch(action);
}
// 第三步:store本身是不对数据进行处理的,接收到组件传的action后,会把当前store里的内容和这个action,一起给到reducer。
// reducer就能接受到store之前的内容(state)和传过来的action
// reducer根据action的类型,对数据进行处理,然后把新的数据return出去,返回给store
export default (state = defaultState, action) => {
if(action.type === 'change_input_value'){
// 注意 reducer可以接收state,但是决不能修改state,所以下边要对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 = '';
console.log(newState);
return newState;
}
return state;
}
// 第四步 store拿到最新的state之后,把之前的state替换掉
// 第五步 组件通过store.subscribe方法监听store的变化,并触发绑定的函数
// 在subscribe绑定过函数里,重新获取store里的最新的数据,并修改自己的state
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);
store.subscribe(this.handleStoreChange);
}
handleStoreChange(){
this.setState(store.getState());
}
注意 reducer可以接收state,但是决不能修改state,所以要对state进行深拷贝
ActionTypes的拆分
- 当我们创建action的时候,会定义这个action的type,值是自定义的字符串,但是这里有个问题,当我们使用这个字符串进行action的判断的时候,如果字符串写错了,页面是不会报错的,不利于我们定位问题。这个时候,我们可以对action的type进行拆分,使用常量代替原来的字符串。
// 先在store下创建 actionTypes文件,里边定义types的常量并导出
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
// 在用到action的type的地方,引入刚才定义的常量
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM} from './store/actionTypes';
// 使用的时候,直接用常量代替字符串
handleBtnClick(e){
const action = {
type:ADD_TODO_ITEM
}
store.dispatch(action);
}
- 然后我们在使用过程中,如果不小心写错了变量某个字符,页面就会报错。
使用actionCreators 统一创建 action
- 比较简单功能,action分散在业务逻辑里边还可以,但是当业务逻辑非常复杂的情况下,action到处都是,对后期的测试和管理是非常不方便的。所以,我们要把action拆分到actionCreator 里进行统一的创建管理。
// 在store下创建actionCreators.js文件,我们把action的创建统一放到这个文件里,对action进行统一的管理,
// 主要是为了提高代码的可维护性,方便自动化测试
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM} from './actionTypes';
// 定义一个常量,是一个函数,该函数返回一个对象,对象包含action的type和值
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
})
export const getAddItemAction = () => ({
type: ADD_TODO_ITEM
})
export const getDeleteItemAction = (index) => ({
type: DELETE_TODO_ITEM,
index
})
// 具体的使用,先引入这些actionCreators 里的action
import {getInputChangeAction, getAddItemAction,getDeleteItemAction} from './store/actionCreators';
// 然后使用store.dispatch去触发action
handleInputChange(e){
const action = getInputChangeAction(e.target.value);
store.dispatch(action);
}
handleStoreChange(){
this.setState(store.getState());
}
handleBtnClick(e){
const action = getAddItemAction();
store.dispatch(action);
}
handleItemDelete(index){
const action = getDeleteItemAction(index);
store.dispatch(action);
}
注意Redux的设计使用原则
- 注意Redux的设计使用原则,第一个就是store必须是唯一的,也就是整个应用中只有一个store作为公用存储
- 只有store能够改变自己的内容。(
reducer返回了数据,store会用这个数据自己去替换自己原来的数据
),换句话说这也是为什么reducer不能修改state的内容,state指的就是store里的数据 - Reducer必须是纯函数(
纯函数指的是,给定固定的输入,就一定会有固定的输出,而且不会有任何的副作用
)。如果函数里有ajax请求,setTimeout等异步的时候或与日期相关的时候,就不是固定的输入了,所以Reducer里不能有异步的操作,也不能有跟时间相关的操作
。
export default (state = defaultState, action) => {
if(action.type === CHANGE_INPUT_VALUE){
const newState = JSON.parse(JSON.stringify(state));
// 如果inputValue = new Date().那么就不会是一个固定的值了。是不行的。
newState.inputValue = action.value;
// 如果把newState 换成state,那么就是改变了参数,而对reducer传入的参数的修改是有副作用的。
state.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-DevTools 是一个非常棒的工具,它可以让你实时的监控Redux的状态树的Store,在谷歌扩展程序安装后,要想开发环境下使用,还需配置。
// 未配置之前,创建的store
import { createStore} from "redux";
import reducer from './reducer';
const store = createStore(reducer);
export default store;
// 配置以后,创建是store
import { createStore,applyMiddleware, compose } from "redux";
import reducer from './reducer';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, /* preloadedState, */ composeEnhancers());
export default store;
-
当这个图标变绿色就证明配置成了,可以调试了
image.png
总结Redux的核心API
- createStore 绑我们创建store
- store.dispatch方法,帮我们派发创建的action,这个action会传递给store
- store.getState方法,帮我们获取到store里的所有的数据内容
- store.subscribe方法,帮我们订阅store里数据的改变,只要store发生改变,subscribe方法接收的函数,就会被执行。
如果说store是图书管理员,那么reducer就可以看作是图书记录手册,管理员需要通过记录手册来对数据进行操作。随着项目复杂度的增加,如果reduce存放过多的数据,可能造成代码的低可维护性,这个时候,我们就需要把一个reducer拆分成多个子的reducer,最后再做整合。具体怎么做呢?
-
首先,创建子的reducer,比如我的Header文件夹里存放的都是Header组件的内容,那么这个时候,可以在Header文件夹下创建一个store文件夹,store文件夹下再创建一个reducer.js文件。
-
然后在总的store文件夹下的reducer.js文件里,通过
combineReducers
方法进行整合。
// 把一些小的reducer合并成大的reducer
import { combineReducers } from 'redux';
import headerReducer from '../common/header/store/reducer';
export default combineReducers({
// header是自定义的名字,可随意起名
header:headerReducer
})
image.png
- 整合以后,我们再取值的时候,就需要多一层。
// 没用拆分之前
const mapStateToProps = (state) => {
return {
focused:state.focused
};
}
// 拆分后
const mapStateToProps = (state) => {
return {
focused:state.header.focused
};
}
- 当然了,像上边那样创建子reducer,在总的reducer引入子reducer的时候,层级有可能会很深。
import headerReducer from '../common/header/store/reducer';
-
这个时候,可以给子reducer创建一个出口文件,index.js,然后在出口文件里,把recucer导出
image.png -
然后在总的reducer里引入的时候,就可以这样
image.png
网友评论