React状态管理--Redux
状态管理的思想
- web 应用是一个状态,视图和状态时对应的
- 所有的状态都是可以统一保存的
- 使用状态区影响视图,更利于视图的统一性和操作性。
- Redux 知识 react 的视图层而不是一定和 react 一起使用的
Redux 原理图解
什么时候使用 Redux
- 有复杂的状态需要共享
- 有状态需要传递很多次并且有多次应用
- 有触发一个组件状态改变全局的状态
- 用户多层权限等等多用户用心等等,这种情况使用 Redux 比较好
- 与服务器之间有大量交互
- 其他。。
Redux 使用的原则
- 唯一的数据源 Single Source of Truth
- 状态时只读的 State is read-only
- 数据的修改需要通过纯函数来执行 Changes are made with pure funtion
基本使用方法
安装
npm i redux
# or
yarn add redux
2
3
store 和 reducer
使用 Redux 首先需要创建 store 和 reducers 这两个是配套的
# 在store.js 中
import { createStore } from 'redux'
import reducers from './reducer'
export default createStore(reducers)
# 在reducers.js 中
const initState = {
list: [],
placeHolder: 'hello world'
}
export default (state = initState, action) => {
return state
}
2
3
4
5
6
7
8
9
10
11
12
13
在 组件使用的时候
import React, { Component } from 'react'
import store from './store'
export default class App extends Component {
constructor() {
super()
this.state = {
...store.getState()
}
}
render() {
return <div>{this.state.placeHolder}</div>
}
}
// 这里不推荐使用把 store 完全覆盖this.state
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这个时候界面就会显示 hello world
- reducer 一定要默认导出 state
- reducer 需要有初始值给 reducer 第一次运行使用
- reducer 内部不可以更该 state
- reducer 只能返回新的 state
- reducer 必须是一个纯函数不可以有异步等等操作。
修改 state
需要修改 state 就需要创建 action 和对应的 actionTypes 以及对应 actionTypes 的 reducer
创建 actionTypes
export const ADD_TODO = 'ADD_TODO' export const CHANGE_TODO = 'CHANGE_TODO' //或者 export const TODO = { ADD: 'ADD', CHANGE: 'CHANGE' } export const CART = { ADD: 'ADD', CHANGE: 'CHANGE', DELETE: 'DELETE' }
1
2
3
4
5
- 创建 action.js
// 在action.js 中
import { ADD_TODO, CHANGE_TODO } from './actionTypes'
// 这种方法返回action 的方式成文 actioncreater
export const addTodo = todo => {
return {
type: ADD_TODO,
payload: { todo }
}
}
export const changeTodo = value => {
return {
type: CHANGE_TODO,
payload: { value }
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 在使用的时候,需要 store dispatch 对应的 action 给具体的 reducer
store.dispatch(addTodo(value))
书写对应的 reducer 处理
switch (action.type) { case ADD_TODO: newState = JSON.parse(JSON.stringify(state)) newState.list = [...state.list, action.payload] newState.inputVal = '' return newState case CHANGE_TODO: newState = JSON.parse(JSON.stringify(state)) newState.inputVal = action.payload return newState default: //一定要有默认导出 return state }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 在页面订阅 state 的变化
store.subscribe(this.storeChange) //订阅
storeChange = () => {
//获取state的公共参数 也就是每次获取新的都会被订阅到store
this.setState(store.getState())
}
2
3
4
5
combineReducers 的使用
因为在大型应用中不可能只有一个 reducer 而 createStore 又只接受一个 reducer,并且要求是方法
所以在 redux 中给出了一个方法就是 combineReducers,这个方法可以合并导出 reducers
使用:
import {combineReducers} from 'redux'
import 具体的reducer from './对应的reducer文件'
export default combineReducers({
// 把多个reducer 当作combineReducers的对象参数传入即可
...reducers
})
//把reducers 给store 使用
2
3
4
5
6
7
注意不要手动引入 reducer
combineReducers 接受的是一个对象
异步的 action
因为 reducer 是一个纯函数不可以又异步的操作,所以所有异步的操作都需要在 action dispatch 前完成,在异步完成后手动来 dispatch。
redux-thunk
和redux-saga
常用的 使用方法
#1 安装
npm i redux-thunk
2
使用
// 在 store.js 中引入中间件
// 引入applyMiddleware 应用中间件
import { createStore, applyMiddleware } from 'redux'
// 引入中间件
import thunk from 'redux-thunk'
import rootReducer from './reducers'
//穿件STore
export default createStore(rootReducer, applyMiddleware(thunk))
2
3
4
5
6
7
8
在 action 中
// 传入TOdo参数 之后 会得到dispatch 然后通过手动dispatch 即可异步执行
export const asyncAddTodo = todos => dispatch => {
setTimeout(() => {
dispatch(addTodo(todos))
}, 2000)
}
2
3
4
5
6
中间件增强函数
Redux 中间件在 applyMiddleware 中正常在执行 createStore 的时候智能接受 2 个参数 1 reducer 2 applyMidleware
那么当使用多个中间件的时候需要使用增强函数 compose ,这是有 redux 提供的:
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
const composeEnhancers =
typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// 其他的扩展选项 name, actionsBlacklist, actionsCreators, serialize...
})
: compose
const enhancer = composeEnhancers(
applyMiddleware(...middleware)
// 其他的 增强 这样可以使用多个中间件
)
const store = createStore(reducer, enhancer)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
配合 react-redux 使用
目录结构
使用步骤
- 创建 reducers
- 合并 reducers
- 创建 store createStore
- Provider store={store}
- connect(mapState,{...actionCreators})(component)
- 根据需求做 actionTypes 和 actionsCreater
- 根据 action 修改 reducers
使用方法
引入
react-redux
import React from 'react' import {render} from 'react-dom' import {provider} from 'react-redux' import App from './app' import store from './store' //store是需要Redux穿件的STore //把提供者组件包裹在最外层 这个组件必须接受一个store 属性 这个属性的值就是我们穿件的store render( <Provider store={store}> <App/> </Provider> )
1
2
3
4
5
6
7
8
9
10
11创建 store
// createStore 是用来创建store 的方法
import { createStore } from 'redux'
//引入 合并后的reducer
import rootReducer from './reducers'
//createStore 第一个参数一个是一个 reducer ,如果是多个 在reducer 目录下使用 combineReducers 合并导出
export default createStore(rootReducer)
2
3
4
5
6
- 创建 reducers
# 在 reducers 项目下 index.js文件中
//在实际的项目中 由于只有单一的store 但是状态会有很多 所以需要对reducer进行划分
// 而 createStore的参数又指接受一个reducer 所以 redux 使用 combineReducers 合并
//多个reducer 继续引入 使用 combineReducers合并导出
import {combineReducers} from 'redux'
import 具体的reducer from './对应的reducer文件'
export default combineReducers({
// 把多个reducer 当作combineReducers的对象参数传入即可
...reducers
})
2
3
4
5
6
7
8
9
10
具体的 reducer 中
# 在 reducers 项目下 cart.js文件中 ,在此文件中定义初始化的state状态 和 具体action修改动作
//actionType 是action状态 把状态统一管理
import actionType from '../actions/actionType'
//这是一个初始化的状态
const initState =[{
id:1,
context:'todo somethings',
componented:'false'
}]
//创建reducer 这是一个纯函数 reducer 固定写法是
//两个参数 第一个就是state 必须又一个初始值 第二个是action
export default (state=initState,action)=>{
// 根据不同的actionType 做不同的处理
//每次返回一个新的state
switch(action,type){
case actionType.TODO_ADD:
return //TOdo sh
// 默认 不做任何处理 必须有这一行
default :return state
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
创建 actions
# actions 中有 两种文件 # 1. actionType.js 所有的状态进行统一管理并且 必须是 大写的函数名对应大写函数值 标识常量 # 2. 具体的action 例如 todo.js 内部返回actioncreater 需要有type 和payLoad 载荷 import actionType from './actionType.js' //action 有两种写法 //1. 这是标准的action 但是不方便传递参数 因此写成函数式 export const addTodo{ type:actionType.TODO_ADD, // payload:{ // todo // } } //2. 函数式action 方便传递参数 并且在和redux-react 集成 connenct 的时候 是可以直接使用的 // 这种actioncreater 是常用的 export const addTodo=(todo)=>{ return { type:actionType.TODO_ADD, payload:{ todo } } } export const changeTodo=(todo)=>{ return { type:actionType.TODO_CHANGE, payload:{ todo } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31获取属性 链接到 store 使用 connnet
在 provider 包裹的组件中都可以使用 redux.connect 做链接
import React,{Component} from 'react'
import {connect} from 'redux-react'
//导入actionCreater
import {addTodo,changeTodo} from '../../todo.js'
class TodoList extends Component {
render(){
return (
)
}
}
//mapStateToprops 这里的state 实际上就是store.getState()的值
const mapState=(state)=>{
return {//这里return了什么在 this.props 中就可以获取什么
todos:state.todo
}
}
//connect 方法执行之后才是一个高阶组件
//connect 方法有四个参数 常用前两个
// 第一个参数 mapStateToprops 作用是把store内的state注入到 当前组件的props 上
//第二个参数是可以是mapDispatchToProps,这个的主要作用是把action生成的方法注入到当前组件props上面
//直接第二个参数传递一个对象这里的对象就是actionCreaters 只要是传入了actionCreaters,在组件内就可以通过
//this.props.actionCreater来调用,在调用之后就会自动把他内部的actiondispach出去
export default connect(参数1,参数2,参数3,参数4)(TodoList)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
配合 Immutable.js 使用
因为再reducer 中state状态时不可以被修改的,因此如何有效率的拷贝state和修改返回state就是一个技术,再正常情况下修改state,难免会修改到原来的state忘记返回新的,因此使用Immutable.js 可以解决问题
安装
yarn add immutable
# or
npm i immutable
2
3
示例
Immutable 可以通过共享部分对象来创建新的对象,对象被创建就不能被修改数据,对任何 Immutable 的任何操作包括添加==更改和删除==都会产生一个==新的 Immutable 对象==. 他的原理是实现了 ==persistent (持久化数据解构)==也就是使用旧数据创建新的数据的时候保证==旧的数据不可变==。
为了避免深拷贝把所有节点都复制造成的性能损耗,Immutable 使用了 structural sharing(节点共享),即为如果只有修改这个节点和受他影响的父节点,其他节点进行共享,也就是节点共享
import {map,fromJs} from 'Immutable'
let m1 =Map({a:1,b:2,c:3,d:{aa:1}})
clog(m1[a])//1 //原数据解构
let m2 = m1.set('a','11')
clog(m2.a)//2 新数据解构
clog(m1.a)//1 原数据解构
clog(m2===m1)//false 新内存地址
clog(m1.d===m2.d)//true 说明d的内存地址相同 这个节点共享了
//如果更改D 下面的aa则
let m3 = m1.set(['d','aa'],'22222')//欲修改的子节点在数组后面 是按从前向后排列的
//fromJS 把原生的JS对象转换成一个Immutable 数据解构对象
let m4 = fromJS({a:1,b:2,c:3,d:{aa:1}})
let m5 = m4.setIn([d,aa],'333')//添加 多级
clog(m4.getIn(['d','aa']))//获取多级的方法
let m6 = m4.updateIn(['d','aa'],value=>value+1)//更新
let m6 = m4.updateIn(['d'],value=>value(hhhh)=22233)//更新
let m7 =m4.mergeDeep({d:{bb:222}})//深度合并
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
上面的库很大而且需要重新特定的方法,所有后面出现了 seamless-Immutable 这个库可以无缝的使用 js 大小只有 2K 这个库只支持 array 和 object
let immutable = require('seamless-immutable')
let m1 = immutable({a:1,b:2,{info:{name:'zhangsan'}}})
let m2 = m1.merge({a:2,b:3,c:6)
clog(m1.info.name)//使用js输出对象的方式使用即可
clog(m2.b)
2
3
4
5
使用再rendux中
首先因为需要把state变为Immutable对象所以需要使用redux中间件 redux-immutable
import {
combineReducers
} from 'redux-immutable';
import {
createStore
} from 'redux';
const initialState = Immutable.Map();
const rootReducer = combineReducers({});
const store = createStore(rootReducer, initialState);
//这样就把 state再reducer 导出的时候变为一个immutable对象
2
3
4
5
6
7
8
9
10
11
12
13
14
使用:
import Immutable from 'immutable';
const initialState = Immutable.fromJS({
name: null
});
export default (state = initialState, action) => {
if (action.type === 'SET_NAME') {
return state.set('name', action.payload);
}
return state;
};
2
3
4
5
6
7
8
9
10
11
12
或者使用 seamless-immutable 与 redux-seamless-immutable 配合使用
//再配置的时候
import { combineReducers, routerReducer, stateTransformer } from 'redux-seamless-immutable'
import { createStore, applyMiddleware } from 'redux'
import createLogger from 'redux-logger'
import reducer from './reducers'
const rootReducer = combineReducers({ // 配置reducer
reducer,
routing: routerReducer //配置解决router和redux冲突
})
const loggerMiddleware = createLogger({//配置state解析
stateTransformer: stateTransformer
})
const store = createStore(//加载中间件
rootReducer,
applyMiddleware(
loggerMiddleware
)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22