一前言
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
Redux是React生态中著名的状态管理工具,学习它的思想并理解其中的原理对我们日后的开发有莫大的帮助,本文将以简单易懂的语言来讲解Redux实现的原理,文章最后会附上Redux的简易实现。
二原理解析
createStore
createStore(reducer, [preloadedState], enhancer)
createStore会返回一个唯一的store,用来存放应用中所有的state
store方法
getState()
dispatch()
subscribe(listener)
先暂时不管createStore的第三个参数,我们可以基于发布订阅的设计模式来实现createStore
function createStore(reducer, initState) {
let cacheData = initState, cacheCallback = []
function getState() {
return cacheData
}
function dispatch(action) {
cacheData = reducer(cacheData,action)
cacheCallback.forEach(fn => {
fn()
})
}
function subscribe(listener) {
cacheCallback.push(listener)
}
return { getState, dispatch, subscribe }
}
复制代码
来测试一下
const action = {
type: 'add'
}
const reducer = (state, action) => {
switch (action.type) {
case 'add': return {
...state,
num: state.num + 1
}
default: return state
}
}
let store = createStore(reducer, { num: 0 })
store.subscribe(callback)
store.dispatch(action)
function callback() {
console.log(`callback触发,num为${store.getState().num}`);
}
复制代码
没问题,触发一次dispatch num加一
applyMiddleware
接下来是applyMiddleware的实现,中间件这部分是我认为Redux实现最巧妙的一部分
function applyMiddleware(store,middlewares){
middlewares = middlewares.reverse()
middlewares.forEach(middleware=>{
store.dispatch = middleware(store)
})
}
function logMiddleware1(store){
let next = store.dispatch
return (action)=>{
console.log("进入logMiddleware1");
next(action)
console.log("离开logMiddleware1");
}
}
function logMiddleware2(store){
let next = store.dispatch
return (action)=>{
console.log("进入logMiddleware2");
next(action)
console.log("离开logMiddleware2");
}
}
复制代码
来测试一下,这里只增加了applyMiddleware一行
let store = createStore(reducer, { num: 0 })
+applyMiddleware(store,[logMiddleware1,logMiddleware2])
store.subscribe(callback)
复制代码
没问题!跟我们想的一样,applyMiddleware
通过修改store.dispatch
的指向来实现对原本dispatch
的包装,又利用闭包来保存每一个中间件的store.dispatch
。接下来我们添加上thunk这个中间件,来看下Redux官网是怎么使用它的:
// fetchTodoById is the "thunk action creator"
export function fetchTodoById(todoId) {
// fetchTodoByIdThunk is the "thunk function"
return async function fetchTodosThunk(dispatch, getState) {
const response = await client.get(`/fakeApi/todo/${todoId}`)
dispatch(todosLoaded(response.todos))
}
}
function TodoComponent({ todoId }) {
const dispatch = useDispatch()
const onFetchClicked = () => {
// Calls the thunk action creator, and passes the thunk function to dispatch
dispatch(fetchTodoById(todoId))
}
}
复制代码
ok,没问题。我们只需要检查一下传进来的是否是一个函数,如果是一个函数则直接执行这个函数,这个函数执行完毕后会重新dispatch一次,所以我们可以这样写:
function thunk(store){
let next = store.dispatch
return (action)=>{
if(typeof action === 'function'){
action(store.dispatch)
}else{
next(action)
}
}
}
复制代码
这里提一下action里面使用store.dispatch和使用next有什么不一样;可能有同学会因为next=store.dispatch
而产生误解,实际上在applyMiddleware
函数在执行的时候next
的指向就已经确认了,而store.dispatch是在applyMiddleware
执行完之后得到的,指向最外层中间件,有兴趣的小伙伴可以打印下next
和store.dispatch
看看是不是和你想的一样。
我们这里更改一下dispatch的使用,用一个setTimeout模仿异步函数
store.subscribe(callback)
-store.dispatch(action)
+store.dispatch((dispatch)=>{
+setTimeout(()=>{
+console.log("I am async function");
+dispatch(action)
+},1000)
+})
function callback() {
console.log(`callback触发,num为${store.getState().num}`);
}
复制代码
来执行一下
现在,我们把createStore
的第三个参数enhancer加进去,如果enhancer存在,则把createStore和reducer,inistate传入这个函数,通过这个函数来构造store
function createStore(reducer, initState,enhancer) {
let cacheData = initState, cacheCallback = []
if(enhancer){
return enhancer(createStore)(reducer,initState)
}
function getState() {
return cacheData
}
function dispatch(action) {
cacheData = reducer(cacheData, action)
cacheCallback.forEach(fn => {
fn()
})
}
function subscribe(listener) {
cacheCallback.push(listener)
}
return { getState, dispatch, subscribe }
}
复制代码
再对applyMiddleware这个函数作一下处理,返回一个增强过后的store;看懂这里的代码需要一点前置知识
- 箭头函数的隐式返回
const func = (a)=>(b)=>(c)=>(a+b+c)
复制代码
等价于:
function func(a){
return function(b){
return function(c){
return a+b+c
}
}
}
复制代码
- compose函数
该函数用来从右向左组织多个函数,即compose(funcA, funcB, funcC) ==compose(funcA(funcB(funcC())))
。对reduce不熟悉的同学可以看下这篇文章:Redux之compose
function compose(...chain) {
if (chain.length === 0) return (arg) => arg
if (chain.length === 1) return chain[0]
else {
return chain.reduce((pre, cur) => (arg) => (pre(cur(arg))))
}
}
复制代码
最后是applyMiddleware的实现:
const applyMiddleware = (...middlewares) => (createStore) => (reducer, initState) => {
const store = createStore(reducer, initState)
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
let dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
复制代码
最后贴上完整代码
JS部分:
function createStore(reducer, initState, enhancer) {
let cacheData = initState, cacheCallback = []
if (enhancer) {
return enhancer(createStore)(reducer, initState)
}
function getState() {
return cacheData
}
function dispatch(action) {
cacheData = reducer(cacheData, action)
cacheCallback.forEach(fn => {
fn()
})
}
function subscribe(listener) {
cacheCallback.push(listener)
}
return { getState, dispatch, subscribe }
}
const applyMiddleware = (...middlewares) => (createStore) => (reducer, initState) => {
const store = createStore(reducer, initState)
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
let dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
function compose(...chain) {
if (chain.length === 0) return (arg) => arg
if (chain.length === 1) return chain[0]
else {
return chain.reduce((pre, cur) => (arg) => (pre(cur(arg))))
}
}
const logMiddleware1 = (store) => (next) => (action) => {
console.log("进入logMiddleware1");
next(action)
console.log("离开logMiddleware1");
}
const thunk = (store) => (next) => (action) => {
if (typeof action === 'function') action(store.dispatch)
else (next(action))
}
const logMiddleware2 = (store) => (next) => (action) => {
console.log("进入logMiddleware2");
next(action)
console.log("离开logMiddleware2");
}
复制代码
HTML部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redux</title>
</head>
<body>
hello world!
</body>
<script src="./c.js"></script>
<script>
const action = {
type: 'add'
}
const reducer = (state, action) => {
switch (action.type) {
case 'add': return {
...state,
num: state.num + 1
}
default: return state
}
}
let store = createStore(reducer, { num: 0 }, applyMiddleware(logMiddleware1, thunk, logMiddleware2))
store.subscribe(callback)
store.dispatch((dispatch) => {
setTimeout(() => {
console.log("I am async function");
dispatch(action)
}, 1000)
})
function callback() {
console.log(`callback触发,num为${store.getState().num}`);
}
</script>
</html>
复制代码
总结
在看源码之前先去看了大佬们写的相关文章,了解其原理后在看源码会事半功倍。redux,包括react-redux的源码都不像想象中那么难啃,也推荐大家在学习源码的过程中先从这种小而巧的库入手。另外,我的实现还很简陋,有不足之处或者写错的地方欢迎在评论区指出。