什么是Redux
官方描述:
Redux 是 JavaScript 状态容器,提供可预测化的状态管理
Redux工作流程
- 用户通过 view 触发
action, 是通过dispatch来派发action的 - 然后调用
reducer方法,传入当前的state和action, 通过计算返回新的 state - 当
state发生变化后,就会调用 store 的subscribe监听的函数,来更新视图
下面就是一个简单流程图:
在整个过程中,数据是单向流动的,如果想对其修改,只能通过 dispatch 派发 action
Redux的组成
store
store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 store。
import { createStore } from 'redux';
const store = createStore(reducer);
上面代码中,creatStore函数接收了 reducer函数 作为参数,返回新生成的store对象
store有两个核心方法,分别是 getState 和 dispatch 。前者用来获取store的内容(state) ,后者用来修改store的内容
state
stroe 对象中存放的数据
action
state 的变化,会导致 view 的变化。但是,用户接触不到 state,只能接触到 view。所以,state 的变化必须是 view 导致的,action 相当于是 view 发出的通知,表示 state 应该要发生变化了。
// action 是行为的抽象,它是一个对象。其中的type属性是必须的,表示 action 的名称。其他属性可以自由设置
const add = {
type: 'ADD_TODO',
payload: 'Learn Redux'
};
actionCreator
在我们写代码的时候 每次更新state的时候,由于action是对象,我们每次写的时候都要写一个 type 属性,这样感觉会比较冗余,这是我们可以自定义一个函数,在里面指定好 type,然后传入不同的修改信息就可以了,这个函数就叫做 actionCreator
- action 是一个对象,用来描述 state 的变化
- actionCreator是一个函数,用来创建 action,大家不要混淆了哦
举个🌰
// addTodo函数就是一个 actionCreator。
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
// 通过 addTodo 创建了一个action
const action = addTodo('Learn Redux');
dispatch
dispatch 是 view 发出 action 的唯一方法 dispatch(action)
import { createStore } from 'redux';
const store = createStore(fn);
store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});
// 用 actionCreator 的话就可以这么写
store.dispatch(addTodo('Learn Redux'))
reducer
store 收到 action 以后,必须给出一个新的 state,这样 view 才会发生变化。而这正是 reducer 要做的事情,更新 state
reducer:是一个纯函数,用来修改store的状态,接收两个参数state,action。
const defaultState = 0;
//定义了一个reducer函数
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD':
return state + action.payload;
default:
return state;
}
};
//调用函数,返回新的state
const state = reducer(1, {
type: 'ADD',
payload: 2
});
上面代码中,reducer函数收到名为 ADD 的 action 以后,就返回一个新的 state,作为加法的计算结果。
但是在实际应用中,reducer 函数并不需要像上面这样子调用,store.dispatch 方法会触发 reducer 的自动执行,为此store 需要知道 reducer 函数,做法就是在生成 store 的时候,将 reducer 传入createStore 方法。
subscribe
store允许使用store.subscribe方法设置监听函数,一但 state 发生变化,就会自动执行这个函数
import {createStore} from "redux"
const store = createStore(reducer)
store.subscribe(listener)
显然,只要把view的更新函数(对于react项目,就是组件的render方法或者setState方法)放入listener,就会实现view的自动渲染
store.subscribe 方法返回一个函数,调用这个函数就可以解除监听
let unsubscribe= store.subscribe(()=>{
console.log(store.getState())
})
unsubscribe()
Redux的特点
单向数据流,在单向数据流中,数据的变化的可预测的。
源码
在这里我们看下 redux的源码,其实并不复杂,主要导出了已下这几种方法供我们使用
createStore
这里首先看第一个 createStore,使用方法:
import { createStore, applyMiddleware } from "redux";
const store = createStore(
reducer,
initState,
applyMiddleware(middleware1, middleware2)
);
createStore方法接收三个参数
- 第一个为 reducer, 是一个纯函数
- 第二个为初始化的 state
- 第三个是中间件对
dispatch进行扩展
我们看createStore 源码,去掉了ts部分,建议耐心看完~
export default function createStore(
reducer,
preloadedState,
enhancer
) {
// ...... 去掉了对参数类型校验部分
// 这里如果第二个参数为函数且第三个参数未传 就会认为第二个参数是 enhancer, preloadedState 为 undefined
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// 对传入的中间件做校验 必须是一个函数
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(
enhancer
)}'`
)
}
// 如果有传入中间件函数 则会接收 createStore 作为参数,并返回一个函数把 reducer preloadedState 作为参数传入
return enhancer(createStore)(
reducer,
preloadedState
)
}
// 这里则是对 reducer类型做的校验 必须是函数
if (typeof reducer !== 'function') {
throw new Error(
`Expected the root reducer to be a function. Instead, received: '${kindOf(
reducer
)}'`
)
}
// 记录当前的 reducer
let currentReducer = reducer
// 记录当前 store 中存的state
let currentState = preloadedState
// 用于记录在 subscribe中订阅的事件
let currentListeners: (() => void)[] | null = []
// 副本 其实主要对listeners的操作主要在 nextListeners
let nextListeners = currentListeners
// 记录当前是否正在进行 dispatch
let isDispatching = false
// 如果 nextListeners 和 currentListeners 相等的话 更改nextListeners让指向 currentListeners 的副本,确保他两个不指向同一个引用
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// ------ getState 返回当前的 currentState
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
// ------ 监听函数 收集依赖 入参是一个函数
function subscribe(listener: () => void) {
if (typeof listener !== 'function') {
throw new Error(
`Expected the listener to be a function. Instead, received: '${kindOf(
listener
)}'`
)
}
// 禁止在 reducer 的时候调用 dispatch
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
// 防止多次调用 unsubscribe 函数
let isSubscribed = true
// 在这里确保让nextListeners 和 currentListeners不指向同一个引用
ensureCanMutateNextListeners()
// 对 nextListeners 进行操作 push listener
nextListeners.push(listener)
// 返回解绑函数
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
isSubscribed = false
// 同样的操作 确保 确保 他两不指向同一个引用
ensureCanMutateNextListeners()
// 删除操作 还是对 nextListeners 进行修改
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
// ------ 更新state 触发订阅
function dispatch(action) {
// 参数校验 可以忽略
if (!isPlainObject(action)) {
throw new Error(
`Actions must be plain objects. Instead, the actual type was: '${kindOf(
action
)}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
)
}
// 如果正在dispatch 则不允许调用 防止在reducer中调动dispatch造成死循环
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
// dispatch 只能执行完一个再执行另一个 相当于加锁操作 finally 后才允许继续 dispatch
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 把 nextListeners 赋值为 currentListeners listeners, 然后遍历监听的listener数组,触发订阅
/**
提问:为什么要用nextListeners currentListeners 两个数组?按道理发布订阅模式只有一个listeners就可以的
分析:
我们在store中用nextListeners currentListeners 两个数组
但是在订阅(subscribe),解除订阅(unsubscribe)都是用的 nextListeners
在更新订阅的时候,也就是下面,listeners = currentListeners = nextListener,
遍历用的listeners,虽然这里是相等的,但是在之后新增 订阅,解除订阅之前都会通过 ensureCanMutateNextListeners 让他两个指向不同的引用
答:
所以在遍历的时候使用currentListeners, 要对数组操作的时候就用nextListeners, 就是为了确保在下面遍历过程中不出问题
如果是同一个引用,在遍历的时候我们去解除一个订阅,就会导致数组少了一项,从而导致最遍历缺失(因为在react中,我们可以在A组件执行的时候调用 unsubscribe 去解除B组件的订阅)
eg:
const unsbscribeA = store.subscribe(() => {console.log('a')})
srore.subscribe(() => {
unsbscribeA()
console.log('b')
})
srore.subscribe(() => {console.log('c')})
在上面我们监听了三个函数,但是在执行第二个的时候我们调用了 unsbscribeA, 则会 在currentListeners 里删除对应项,如果我们只用了一个监听数组的话,则就会导致遍历到第二项的时候,数组被删了一个,第三个监听的函数就不会被执行,引发异常
*/
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// ------ 替换 reducer
function replaceReducer(nextReducer){
if (typeof nextReducer !== 'function') {
throw new Error(
`Expected the nextReducer to be a function. Instead, received: '${kindOf(
nextReducer
)}`
)
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
return store
}
// 执行一次 dispatch 用来初始化数据
dispatch({ type: ActionTypes.INIT })
const store = ({
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable // 内部方法 忽略不看
}
return store
}
getState
通过这个方法可以获取到最新的 state
// 比较简单 就是返回了当前的state
function getState(){
return currentState
}
replaceReducer
替换当前的reducer 并重新初始化state
function replaceReducer(nextReducer){
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
return store
}
subscribe
在 redux 里 subscribe, dispatch 表示 发布-订阅者模式
subscribe用来注册监听事件,,收集依赖 到listeners数组中, 并返回一个函数用来解绑监听函数
dispatch
是用来派发 action 修改state的唯一方式
当 dispatch(action)后,redux会调用reducer并传入action, state执行完成后返回新的 state,最后将listeners数组中的函数遍历依次执行,触发订阅
具体实现逻辑请看上面源码中的注释~