Rredux是什么?
Redux是JavaScript应⽤用的状态容器器。它保证程序⾏行行为⼀一致性且易易于测试。
- React Conponent: 我们开发的的React组建;
- 当组建的内容要发生改变的时候;我们要发起一个
dispatch
,dispatch
去派发action
,action
里面会包裹{type,paylod}
Reducer
就是制定修改规则的纯函数;接收一个旧的state,和action返回一个新的state永远不要在 reducer ⾥做这些操作:
- 修改传⼊参数;
- 执⾏有副作⽤的操作,如
API
请求和路由跳转;- 调⽤⾮纯函数,如
Date.now()
或Math.random()
。- 共享的数据存储在
Store
里面(state)- 更新完的数据
Store
传给组建
安装Rredux
yarn add redux
Readux的使用
创建store和制定reducers规则
src/store/index.js
import { createStore } from 'redux'
// 定义state初始化和修改规则
function createReducer(store=1,{type,payload=1}){
// type: action 的修改规则,用于switch判断
// paylod,当发起dispatch传进来的参数
console.log(store, 'store')
switch (type) {
case 'ADD': // 加法规则
return store+payload
break;
case 'MINUS': // 减法规则
return store-payload
break;
default: // 默认导出规则
return store
break;
}
}
const store = createStore(createReducer) // 定义store里面的修改规则
export default store
写一个组建
src/pages/ReaduxPage.js
import React, {Component} from 'react'
import store from "../store/index";
export default class ReaduxPage extends Component{
constructor(){
super()
}
render(){
return(
ReaduxPage
获取到store里面设置的state
{store.getState()}
点击按钮,触发dispatch
)
}
}
再或者改成,方法调用的方式
add=()=>{
store.dispatch({type:'ADD', payload:100})
}
// 定一个一个方法去使用,结果都是一样的都是去调用dispatch
测试点击是否生效,我们在 store中有进行打印。
说明点击数据修改了,dispatch已经发送,但是页面没有重新渲染。
解决办法:redux内部是有发布订阅store.subscribe
在组建中,我们添加一个生命周期。// 在组建挂载完成这个组建中 componentDidMount(){ // 当store中数据发生改变时,发起订阅。 store.subscribe(()=>{ // 在数据发生改变的时候,强制页面渲染 this.forceUpdate() }) }
Redux的实现步骤
- createStore 创建store
- reducer 初始化、修改状态函数
- getState 获取状态值
- dispatch 提交更新状态
- subscribe 变更更订阅
自己实现Redux
了解了Redux的实现之后,现在来自己实现一个Redux
首先 创建一个 createStore.js
目录结构: src ├──Myredux # 自己的redux文件 ├──────index.js # 导出文件 ├──────createStore.js # createStore
在
/src/store/index.js
中,其实我们就引用了redux的createStore,import { createStore } from 'redux'
首先在createStore.js 中去创建三个使用到的方法
export default function createStore (){
function getState(){
return null
}
function dispatch(){
return null
}
function subscribe(){
return null
}
return{
// 获取state
getState,
// dispatch
dispatch,
// 订阅事件
subscribe
}
}
createStore 接收一个参数reducer
;在调用使用createStore的时候是这样的:const store = createStore(createReducer) // 定义store里面的修改规则
完善getState、dispatch、subscribe
export default function createStore (reducer){
// console.log(reducer,'reducer')
// 内部定义state初始值,
let currentState = null;
// 所有的监听记录下来做一个数组
let currentListeners = []
// getState:返回当前的state
function getState(){
return currentState
}
// dispatch 接收一个action: {type:'ADD', payload:''},改变 state
function dispatch(action){
// 调用传进来的 reducer方法,获取到更新后的newstate
currentState = reducer(currentState,action)
// 当数据修改完了之后,去发布订阅
// 将监听里面的事件做一次执行
currentListeners.forEach(lister=>lister())
}
// subscribe 接收一个监听函数lister,监听肯定是存在多个
function subscribe(lister){
// 每加进来一个lister,就push进去currentListeners
currentListeners.push(lister)
}
return{
// 获取state
getState,
// dispatch
dispatch,
// 订阅事件
subscribe
}
}
有订阅就必须要有 取消订阅
有订阅,必须要有取消订阅,组建都没有了,还在执行里面的方法,执行
this.forceUpdate
是会报错的.
订阅是在生命周期中去订阅的,取消就需要在componentWillUnmount中去销毁
在组建中src/pages/ReaduxPage.js
// 在组建挂载完成这个组建中 componentDidMount(){ // 当store中数据发生改变时,发起订阅。 this.unsubscribe = store.subscribe(()=>{ // 在数据发生改变的时候,强制页面渲染 this.forceUpdate() }) } // 在组建销毁的时候 componentWillUnmount(){ // 如果有订阅 if(this.unsubscribe){ // 执行取消订阅 this.unsubscribe() } }
在当前我们写的createStore中subscribe方法中,还需要在做取消订阅修改 subscribe方法
// subscribe 接收一个监听函数lister,监听肯定是存在多个
function subscribe(lister){
// 每加进来一个lister,就push进去currentListeners
currentListeners.push(lister)
// 返回一个取消订阅的方法
// 最粗暴的方法就是,将currentListeners置空,当然你也可以选择用filter找到当前的订阅,之后进行删除
return ()=> {currentListeners=[]}
}
异步更新
目前我们只是完成了同步的状态更新,接下来写异步的更新
Redux只是个纯粹的状态管理器,默认只⽀持同步,实现异步任务 ⽐如延迟,网络请求,需要中间件的支持,⽐如我们使⽤最简单的
redux-thunk
和redux-logger
。
中间件就是⼀个函数,对store.dispatch
⽅法进行改造,在发出Action
和执⾏Reducer
这两步之间,添加了其他功能。首先安装插件
yarn add redux-thunk redux-logger
redux-logger 记录日志的中间件。
redux-thunk的使用
首先,我们先用rendux 导入来看下效果
在src/store/index.js
import {createStore, applyMiddleware} from 'redux' // applyMiddleware 使用中间件 import thunk from 'redux-thunk' import logger from 'redux-logger'
// 导出store时,加上插件thunk const store = createStore(createReducer, applyMiddleware(thunk)) // 定义store里面的修改规则
来测试导入的thunk是否生效,我们在组建中执行一个异步更新,调用disipach
在src/pages/ReaduxPage.js
Asyadd=()=>{ store.dispatch(()=>{ setTimeout(() => { store.dispatch({type: 'ADD'}) }, 1000); }) }
or 或者这样写,因为中间件会判断你当前传进来的数据类型,不是一个object,而是一个函数,将会给函数传入参数值
(dispatch,getState)
// 改写 Asyadd=()=>{ store.dispatch((dispatch, getState)=>{ setTimeout(() => { dispatch({type: 'ADD'}) }, 1000); }) }
可以引入logger看一下,就是打印的日志
`
applyMiddleware(thunk, logger)
自己实现applyMiddleware
首先创建文件src/Myredux/applyMiddleware.js
export default function applyMiddleware(){
}
在 src/Myredux/index.js
中导入和导出
// 将 createStore 导入
import createStore from './createStore'
// 将 reudx使用中间件的方法导入
import applyMiddleware from './applyMiddleware'
// 导出
export {
createStore,
applyMiddleware
}
在store中去使用我们自己的redux
import { createStore,applyMiddleware} from '../Myredux'
等等等....等一下,现在的页面,我们自己的写redux还没有实现, state的初始值
所以我们的createStore
还要进行修改一下。在源码中,是这样进行处理的:在createStore中去执行一下dispatch,type的内容只要保持唯一性就可以了,源码是写的随机的字符串
。
// 内部定义state初始值,
let currentState;
// 在dispatch之后去手动执行一遍dispatch,type的值随便写
dispatch({type:'AAAASAJKHSDFH'})
插曲结束
现在我们来实现,加强版的dispatch,现在写的是只能接收一个对象。
有些许的绕这个思路,我后面放一个思维导图,尽量说详细一些。这块也是看了很久。
首先我们要明白,为什么要加强dispatch。是因为原本redux的dispatch只能接收一个对象,而现在我们引入了thunk,需要修改dispatch,让它自它接收处理异步,promise
在src/Myredux/createStore.js中进行调整
export default function createStore (reducer, enhancer){
// enhancer 加强版
// 主要就是加强dispatch
if(enhancer){ // 首先判断 有没有 传入这个 redux使用中间件的方法 ‘applyMiddleware’
// 如果存在
// 首先我们要明确,中间件其实是对dispatch进行了加强,所以还是需要用到createStore,对它进行处理;dispatch还是会用到reducer这个参数。
// dispatch二次包装
return enhancer(createStore)(reducer) //返回一个加强版的store
}
applyMiddleware
其实就做了一件事,就是返回新的store,在这其中加强了dispatch
src/Myredux/applyMiddleware.js
export default function applyMiddleware(...middleware){
// ...middleware 接收的参数,就是中间件
// 1.首先明确返回值 是一个函数,因为我们需要获取到createStore里面的dispatch
return createStore=>reducer=>{ // 双箭头函数,就是少了一层return包裹,可以看下ES6
// 返回store
let store = createStore(reducer)
// 获取到当前的dispatch
let dispatch =store.dispatch
// 3.来写一个加强的dispatch
........
// 2. 函数执行的结果,是返回新的sotore和加强版的dispatch
return {
...store,
dispatch // 加强版本的dispatch
}
}
}
applyMiddleware完整版
export default function applyMiddleware(...middleware){
// ...middleware 接收的参数,就是中间件
// 1.首先明确返回值 是一个函数,因为我们需要获取到createStore里面的dispatch
return createStore=>reducer=>{ // 双箭头函数,就是少了一层return包裹,可以看下ES6
// 返回store
let store = createStore(reducer)
// 获取到当前的dispatch,原版的dispatch
let dispatch =store.dispatch
// 3.来写一个加强的dispatch
// 最终的目的还是要获取到type,更新state
// 定义 mids需要执行的api,也就是需要用到的参数
const midApi={
getState: store.getState,
// dispatch,需要在包一层,dispatch是派发方法的,每个对象都去派发方法,防止被相互污染
dispatch:(actions, ...args)=>dispatch(actions, ...args)
}
// 为什么要将,传进来的参数连起来,因为每次需要去执行,而不能让用户自己手动执行
// 将middleware连起来 (thunk, logger)
// thunk的执行需要dispatch logger日志的打印需要oldstate 和 newstate
const middlewareChain=middleware.map(mids=>mids(midApi)) // 将中间件进行遍历执行
//加强dispatch
dispatch = compose(...middlewareChain)(store.dispatch)
// 2. 函数执行的结果,是返回新的sotore和加强版的dispatch
return {
...store,
dispatch // 加强版本的dispatch
}
}
}
// 函数聚合的方法
function compose(...funcs){
if(!funcs.length){
return args =>args
}
if(funcs.length===1){
return funcs[0]
}
return funcs.reduce((a,b)=>(...args)=>a(b(...args)))
}
自己实现redux-thunk
thunk的源码也很简单就14行
function thunk({dispatch, getState}){
//thunk 里面首先执行的可能是一个dipatch,也有可能是一个function,所以在不确定的情况下,我们将它定义为next
return next => action=>{
// 第二种情况:是个回调函数
console.log(next,'next') // 前面聚合的函数,compose()的聚合出来的结果, 目前测试就一个回调,可以理解为dispatch
console.log(action, 'action')
if(typeof action === 'function'){
return action(dispatch, getState)
}
// 第一种情况:如果传进来的是一个对象,那么就直接执行
return next(action)
}
}
点击异步更新dispatch是的打印结果
自己实现redux-logger
function logger ({getState}){
// 在这里也可以获取到dispatch的,但是用不到,我们只需要用到getState。
// logger主要是打印:执行的方法,和oldstate 和 newstate
return next=> action=>{
console.log(next,'next')
console.log('------------')
const preState= getState()
console.log('preState',preState)
console.log('执行的方法:' ,action.type)
const returnValue = next(action)
const nextState = getState()
console.log(nextState)
console.log('------------')
return returnValue
}
}
next的理解:
不用去声明next,是调用的的实参;在compose
方法中的 reduce的第一个参数(a);执行reduce的时候是一层套一层的执行;每一次进行reduce的循环,都会生成一个新的next
自己实现redux-promise
promise 中间件,就是需要在dispatch中去调用promise
首先安装yarn add redux-promise
在组建中执行:src/pages/ReaduxPage.js
promiseMinus=()=>{
store.dispatch(
Promise.resolve({
// 减法
type:'MINUS',
payload:100
})
)
}
redux-promise的实现,需要去引入两个库
yarn add is-promise flux-standard-action
redux-promise的实现
import isPromise from 'is-promise' //判断是不是promise类型
import {isFSA} from 'flux-standard-action' //判断是不是标准的action 有type,payload
function promise({dispatch}){
return next=>action=>{
if(!isFSA(action)){ // 判断是不是标准的action
// 再判断是不是标准的Promise? 是 执行.then 方法:不是就直接执行
return isPromise(action)? action.then(dispatch):next(dispatch)
}
// 是promise
// console.log(action) -----{type: "MINUS", payload: 100}
return isPromise(action.payload)?
action.payload
.then(res => dispatch({...action, payload:res}))
.catch(error => {
dispatch({...action, payload: error, error:true});
return Promise.reject(error)
})
:next(dispatch)
}
}
目前更新到这里.....,最还是看下源码或者开发中对reudx已经很熟练了再来看,要不然很难理解,因为reudx有些绕。