来简单实现一个redux,内附完整代码

一前言

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

ReduxReact生态中著名的状态管理工具,学习它的思想并理解其中的原理对我们日后的开发有莫大的帮助,本文将以简单易懂的语言来讲解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}`);
    }
复制代码

image.png

没问题,触发一次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)
复制代码

image.png
没问题!跟我们想的一样,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执行完之后得到的,指向最外层中间件,有兴趣的小伙伴可以打印下nextstore.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}`);
    }
复制代码

来执行一下

image.png
现在,我们把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的源码都不像想象中那么难啃,也推荐大家在学习源码的过程中先从这种小而巧的库入手。另外,我的实现还很简陋,有不足之处或者写错的地方欢迎在评论区指出。

猜你喜欢

转载自juejin.im/post/7041169977549783054