状态管理工具 redux
(类似vuex) - 推荐使用(共享) -或者( flux mobx)
一、为什么使用状态管理工具,而不是sessionStorage或localStorage?
vuex和redux是以对象形式存在内存中,单向数据流。 sessionStorage存的是字符串(序列化和反序列化),性能差。
二、手动引入store&手写redux
2.1. 安装
cnpm i redux --save
和yarn add redux
2.2. 三大原则
- 单一数据源(唯一仓库)
- 状态State是只读的; 想要修改状态只能修改副本,目的是数据可追溯。
使用纯函数(reducer)来执行修改 — 只要是同样的输入,必定得到同样的输出。对外界没有副作用的函数。
- 不得改写参数
- 不能调用系统 I/O 的API
- 不能调用
Date.now()
或者Math.random()
等不纯的方法,因为每次会得到不一样的结果
共享数据,全局状态,组件交互
2.3. 使用步骤及其结构
1.步骤
引入createStore
import {createStore} from 'redux';
设置初始数据
const initState ={ key:value}
初始值传给reducer -作用:给store传递state和副本修改state,副本替代原state实现修改
reducer(state=initState,action) 纯函数const store = createStore(reducer);
导出 export default store
何处使用就在组件内部导入 或者 在根目录index引入一次,通过context传递store
2.代码
2.1 文件store的index.js
/** 文件store的index.js */
//引入创建仓库的工具
import { createStore } from 'redux'
//引入总的reducer纯函数
import reducer from './reducer'
//仓库是唯一的(参数是函数-回调执行)
const store = createStore(reducer);
export default store;
2.2文件store的reducer.js — 即分模块
重名情况 | vue | react |
---|---|---|
模块重名 | 会报错,或者使用命名空间 | 别重名,action的type一定不能重名 |
mutation重名 | 都执行 | |
action重名 | 都执行 | |
报错 |
注意:有时候页面不更新,就是浅拷贝的锅,只监听到表层的变化,深层次的变化无法更新视图。此时需要深拷贝:
let newState= { ...state }
//浅拷贝,做副本
let newState= JSON.parse(JSON.stringify(state))
//深拷贝(先转成字符串,再转回来),做副本
/** store中总reducer.js */
import {combineReducers} from 'redux' //合并每个组件各自的reducer
import newcounterReducer from '../components/NewConuter/reducer'
import hocReducer from '../components/hoc/reducer'
export default combineReducers({
newcounter: newcounterReducer,
hoc: hocReducer
})
2.3组件中的文件(reducer.js和actionCreator.js)
let newIncrement = { ...state }
//浅拷贝,做副本
let newIncrement = JSON.parse(JSON.stringify(state))
//浅拷贝,做副本
使用immutable
做深复制
/** 组件目录的reducer纯函数 -- 快捷键: rxreducer */
import {Increment, Decrement } from 'store中的actionType'
//状态的初始值
const initialState = {
n: 5,
countList: [2, 3, 7]
}
//修改数据的reducer纯函数。包含state-数据 和 action-修改数据的动作。switch写了return不写break。
//默认导出,导入时自己随便起名字
export default (state = initialState, action = {}) => {
switch (action.type) {
case Increment:
let newIncrement = { ...state } //浅拷贝,做副本
newIncrement.countList[action.payload] = newIncrement.countList[action.payload] * 1 + 1;//修改副本
return newIncrement;//返回副本
case Decrement:
//简洁写法-有条件限制(缺陷),不是所有都可以简写
//比如复杂的判断情景就会不适用
return { ...state, n: state.n*1-1 };
default:
return state;
}
}
/** 组件目录的actionCreator.js - 创建dispatch的action对象 */
import { Increment, Decrement } from 'store中的actionType'
export default {
inIncrement (payload) {
return {
type: Increment,
payload
}
},
inDecrement (payload) {
return {
type: Decrement,
payload
}
}
}
2.4.具体操作(存,取,改)
某个组件的状态,需要共享; 某个状态需要在任何地方都可以拿到; 一个组件需要改变全局状态; 一个组件需要改变另一个组件的状态
1.获取数据
store.getState().模块名.变量
— 存于state
2.修改数据
action的type值千万不要重复,因为每个type都对应着不同的操作。就算分模块后也不能同名
- 引入actionCreator是为了得到action对象
- 组件内部先派发动作:
store.dispatch({type:'一般大写XXX',payload})
action 是一定含有type的一个对象,其他参数根据需求有无 - 动作发给了 reducer 第二个参数 action
action一定有type ,还可能接收一些其他参数 - 根据type修改数据
a. 做 state 的复本let newState = {...state} 浅拷贝,再无瓜葛
b. 修改state的复本对象
c. 返回新的state
3.监听/订阅store数据变化-(vue是放在计算属性里面实现监听)
不能在生命周期中监听store的数据变化。而且不使用subscribe,store数据变了(reducer中可以发现变化了),但是不更新state中的值,因为constructor只执行一回。所以引入subscribe(原理: 把回调函数放进数组里面,当数据变化,会依次执行数组里的函数,达到更新的目的)
constructor(props) {
super(props);
console.log(store.getState());
this.state = {
countList: store.getState().newcounter.countList
};
// 直接写函数就行,不用在回调里面调用
store.subscribe(this.storeListener.bind(this))
}
storeListener () {
//console.log('监听');
this.setState({
countList: store.getState().newcounter.countList
})
}
// 或者 - 不推荐回调去写
constructor(props) {
super(props);
console.log(store.getState());
this.state = {
countList: store.getState().newcounter.countList
};
// 在回调里面调用
store.subscribe(()=>{
this.setState({
countList: store.getState().newcounter.countList
})
})
}
2.5.subscribe-订阅
1-constructor监听(见上面的订阅)
2-componentDidMount监听
2.6.单向数据流图示
2.7.职责单一原则
把组件拆分为容器组件和ui组件
UI组件 只管渲染
容器组件 Container 和store打交道
三、手写组件实现传递store — 仅传递store,派发,监听,获取都需要自己去写
Context上下文对象(生产者和消费者) - 组件之间传值
乌鸦坐飞机
聊一聊我对 React Context 的理解以及应用-18年此博客是老版本语法,所讲的生产者、消费者是新款语法api。博客值得借鉴
只能用属性value传值,不然会出问题。多个用对象
插槽-此处渲染的是可能外层有Router的App组件
MyProvider是最外层,再是Router,再是App
1.MyProvider组件
// components/myprovider/index.js
import React, {Component, createContext} from 'react'
const context = createContext()
const {Provider, Consumer} = context
class MyProvider extends Component{
render(){
return(
<>
{/* 只能用属性value传值,不然会出问题。多个用对象 */}
<Provider value={{...this.props}}>
{/* 插槽-此处渲染的是App组件 */}
{this.props.children}
</Provider>
</>
)
}
}
export {MyProvider, Consumer, context}
2.组件导出的3个内容的具体作用
使用Consumer只能在render中拿到value,在进行渲染
使用contetx设置好静态属性,在其他生命周期可通过this.context拿到value
/1/MyProvider包裹着根目录index.js中App组件,可以传递value
//通过属性props传递给value
<MyProvider store={store}>
<App />
</MyProvider>
{/** 组件中还是要引入Consumer、context以及actionCreator */}
/2/Consumer在任意组件去使用,先引入在使用
//通过Consumer只能在render中拿到value,在进行渲染
<Consumer>
{(value)=>{return <div>value.传递的具体的值</div>}}
</Consumer>
/3/通过contetx在其他生命周期拿到value
static contextType = context//先在组件设置contextType的静态属性
//constructor的第二个参数就是context
constructor(props,context) {
super(props)
this.state = {
变量: context.getState().模块名.变量名
}
}
//其他(不包括render和constructor)的其他钩子和函数-通过this.context拿到store
componentDidMount(){
console.log(类组件的名字.contextType)
console.log(this.context)
//设置好静态属性之后就可通过this.context拿到value,即store
//修改
this.context.dispatch(actionCreator.函数名(payload参数))
}
四、插件 react-redux 实现context传值
即react-redux通过connect自动生成容器组件,只负责UI组件即可
不用自己手写MyProvider组件,作用:把store的所有模块和需要actionCreator全部映射到props上,不需要获取getState()和监听subscribe和派发时dispatch
安装
cnpm i react-redux -S
注意:redux不能就不安了,store还是基于redux的,react-redux只是对用法做了封装import {Provider} from 'react-redux'
替代手写的MyProvider包裹App(最外层)组件内部:
import {connect} from 'react-redux'
import actionCreator from './actionCreator'
组件内引入connect高阶组件。高阶函数的作用:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
作用:第一个参数传入((state)=>state,actionCreator)后。1:把Provider的属性(即仓库里的store)store的模块数据映射到当前组件的props上,并且自动执行store.subscribe监听数据变化。2:把actionCreator中的每个动作映射到props,可直接使用
4.1不对connect参数做改变
import React,{Component} from 'react'
import {connect} from 'react-redux'
import actionCreator from './actionCreator'
class 组件名 extends Component{
render(){
let n = this.props.模块名.n;//n是这个模块里的变量
return(
<div>
{n}
<button onClick={this.props.incAction}>+</button>
</div>
)
}
}
//柯里化函数-没别的需求,最简单写法
export default connect((state)=>state,actionCreator)(组件名)
//或者(即第一个参数有两个参数,一个函数,一个actionCreator对象)
const mapState = (state) =>state
export default connect(mapState,actionCreator)(组件名)
/**
柯里化解读
function sum(x,y){return x+y}
function sum(x){return (y)=>{return x+y}}
let sum=(x)=>(y)=>x+y
console.log(sum(2)(3))
*/
4.2.connect高阶组件第一个参数中两个参数解读不同写法!!!!!!important
都是返回对象,对象的每个键值对都是一个映射
4.1.1 对于函数(state)=>state
let mapStateToProps = (state, ownProps)=>({//省略大括号和return,返回一个对象
//1.store中的变量
变量名(可重命名): state.模块名.变量名(需和仓库一一致),
//2.类似于计算属性
变量名(想要的计算属性的变量):state.one.n>18?'先生或女士':'小朋友'
})
// 1.ownProps作为参数,容器组件的参数变化也会引发UI组件重新渲染
// 2.connect省略mapStateToProps,则Ui组件不会订阅store-store的更新不会引起UI组件的更新
// 导出时mapState替代
export default connect(mapStateToProps,actionCreator)(组件名)
4.1.2 对于actionCreator(是一个包含创造action对象的函数的大对象)
//1.仅仅为了改名-原理都是通过解构改名(在传入参数之前-下面即是、在最开始引入actionCreator是导出多个也可以改名、在在使用时从props引入时解构改名)
let {changeAction: 修改后的名字} = actionCreator
let mapDispatch={修改后的名字}
//2.老的写法:通过redux中的bindActionCreators映射所有方法。等价于直接写actionCreator
import {bindActionCreators} from 'redux'
const mapDispatch=(dispatch)=>bindActionCreators(actionCreator,dispatch)
//3.推荐方法:可以实现改名字、可以拿到组件的props以及显示了dispatch,就可以写逻辑控制派发的时机。当然若不需要写时机,则直接用actionCreator传入即可
let mapDispatchToProps=(dispatch,ownProps)=>{
console.log(ownProps)//1.拿到父组件给这个组件传递的props
return{//对象里面包含很多方法
//2.这里可以改名
change(payload){
//3.写逻辑控制派发时机
dispatch(actionCreator.changeAction(payload))
},
inc: function(payload){//function可以省略
dispatch(actionCreator.incAction(payload))
}
}
}
// 导出时mapDispatch替代
export default connect(mapStateToProps,mapDispatchToProps)(组件名)
4.3用法
这时候redux的属性和action动作都挂载到了props上,替代容器组件
五、异步中间件
5.1 redux-thunk
1.异步操作:
a) yarn add redux-thunk
b) applyMiddleware
c)createStore(reducer,applyMiddleware(thunk));
4) actionCreator里面
原来是返回一个对象; 现在方法要返回回调函数,参数就是 dispatch
export default {
IncAction(){
return (dispatch)=>{
异步的动作 成功后再异步动作回调里 使用dispatch发出动作给reducer
}
}
}
2.thunk: 缺点(副作用:sideEffect)-使得actionCreator的职责不纯粹(发动作,还要发请求)
3.thunk在actionCreator中使用
4.错误想法
5.2 redux-promise
5.3 redux-saga
运行-生成器函数(Generator函数)—特征带星号
function *fun(){
yield ‘hello’;
yield ‘world’
return 666 // return最后一次
}
let g = fun();//调用只能拿到一个Generator对象,对象里的next方法取值
g.next();// 第一次返回:{value: ‘hello’}
g.next();// 第二次返回:{value: ‘world’}
g.next();// 第三次返回:{value: 666}
g.next();// 再次返回不会报错,而是返回undefined:{value: undefined}
多次调用,会等待上一次执行完毕
g.next().next();//不能链式调用,因为第一个g.next()没有返回Generator对象
saga在store中的配置
生成器函数