手写redux系列(小白级教程,简单易懂)(

435 阅读5分钟

前言

redux想必很多同学都是用过,但是redux源码是什么样子的呢,难不难呢,怎么下手去实现呢?听说redux有100行代码,但是有一万行注释等等。今天就让我们揭开redux神秘的面纱

曾经对比过单向数据流和双向数据流,无非双向数据流就是把数据更改这些封装到内部,而且是自动触发,而单向数据流是把数据更改这些透出来,然后使用手动触发,所以二者在源码原理方面很像很像

redux是干什么的?

一言以蔽之:redux是做状态管理用的

为什么需要redux?

一言以蔽之:当项目越来越大的时候,组件之间传递数据越来越困难

redux的最大特点是什么?

单向数据流:整个应用的状态集中存储在一个地方,便于管理和跟踪。其实无论是react还是vue其实都采用的单向数据流,单向数据流你可以理解为,父组件可以通过props把数据传给子组件,但是子组件不可以修改props,只能通过父组件传递的方法修改父组件的中的值。

redux的原理

数据仓库(store)+发布订阅,真的就是这么简单,无论是双向数据绑定,还是vuex,redux都是这个原理,当然这是一个大方向。

桶 store

这个store单词我们一看知道干嘛的,数据存储的地方,可以想象成一个仓库,仓库里面的箱子就是初始数据。

我们后面的发布订阅,其实都是在操作仓库里面的箱子。搬进来多少个,搬出去多少个。

梳理发布订阅

我们把发布订阅做一下拆分,发布订阅模式其实主要是实现一个平台,类似youtube网站,上面作者可以发布(dispatch),用户可以订阅(subscribe),管理发布订阅的地方就是reducer

image.png

那他储存这么多东西肯定要有一个store,就是平台上的视频,想看到所有视频,可以getState()

redux的核心api在做些什么事?

// 创建youtube平台,平台得有管理发布订阅的地方reducer和初始数据
const store = Redux.createStore(reducer);
// 获取所有视频
store.getState();
// 用户订阅
store.subscribe(function(){});
// 作者发布
store.dispatch({type:'description'});
// 实现管理中心
function reducer(state, action){};

手写redux

从上面的api可以看出,redux有一个createStore方法,返回一个store对象,store对象有三个方法

// 创建store,返回三个方法
function createStore(reducer) {
    // 获取所有state
    function getState() {}
    // 执行订阅方法
    function subscribe() {}
    // 触发state变化
    function dispatch() {}
    return {
        getState,
        subscribe,
        dispatch,
    };
}

redux主要就实现这个store的,而reducer是由使用者自己实现的

我们创建一个index.html,创建两个按钮,一个+1按钮,一个-1的按钮,然后在span中展示state

<!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>Document</title>
</head>
<body>
    <button id="increment">我是周杰伦,我在油管新发布了一首歌</button>
  <div>我是粉丝小王,油管通知我,我现在只能看到周杰伦的<span id="state">0</span>首歌</div>
  <button id="decrement">我是周杰伦,我在油管删除了一首歌</button>
</body>
<script src="./myRedux.js"></script>
</html>

然后看一下浏览器效果,非常简洁的设计风格

image.png

再写一个script,根据我们的apifunction reducer(state = initialState, action){};然后写我们的reducer

注意:

  • reducer必须是一个纯函数
    • 不得修改传入的参数
    • 不得调用非纯函数,如Date.now()
    • 不得执行有副作用的操作,如API请求和路由跳转
  • 返回一个新的state
  • 必须写上default

所以我们的reducer就闪亮登场了,✨✨✨

reducer是管理中心,只不过redux透出这一部分给使用者自己实现

function reducer(state, action) {
    switch (action.type) {
        case "increment":
            return state + 1;
        case "decrement":
            return state - 1;
        default:
            return state;
    }
}

开始使用我们的createStore方法创建一个store出来

const store = createStore();

看一下控制台,如我们所料打印出了我们的那三个啥都没有的三个方法

图片.png 然后把我们的reducer传给createStore大哥,并加上初始值

<script src="./MyRedux1.js"></script>
<script>
function reducer(state, action) {
    switch (action.type) {
        case "increment":
            return state + 1;
        case "decrement":
            return state - 1;
        default:
            return state;
    }
}
const store = createStore(reducer, 0);
</script>

createStore中接受reducer和初试值,这里需要注意的是state是一个状态,不能createStore方法执行完就消失的,要保存在store中,如何保存,使用闭包呀, 那我们的getState方法也就很容易写出来了

// 创建store,返回三个方法
function createStore(reducer, initialState) {
    // 闭包存储状态
    let state = initialState;
    // 获取所有state
    function getState() {
        return state;
    }
    // 执行订阅方法
    function subscribe() {}
    // 触发state变化
    function dispatch(action) {}
    return {
        getState,
        subscribe,
        dispatch,
    };
}

现在我们点击按钮去用dispatch改变state的值 我们还是掏出我们的密集看一下触发过程,把我们的思路连起来

图片.png

那么dispatch方法也很好写呀

// 触发state变化

function dispatch(action) {
    // 新的state
    state = reducer(state, action);
}

是不是感觉很简单,哈哈😝

那我们写写点击事件

const incrementBtn = document.getElementById("increment");
const decrementBtn = document.getElementById("decrement");
incrementBtn.onclick = function () {
 store.dispatch({ type: "increment" });
};
decrementBtn.onclick = function () {
 store.dispatch({ type: "decrement" });
};

接下来就是改变span节点的值了

const state = document.getElementById("state");
incrementBtn.onclick = function () {
store.dispatch({ type: "increment" });
state.innerHTML = store.getState();
};

decrementBtn.onclick = function () {
store.dispatch({ type: "decrement" });
state.innerHTML = store.getState();
};

然后就发现我们的简版redux就实现,你们说不对呀,还有个subscribe方法没有实现呀,那么subscribe方法是干什么用的呢

subscribe方法其实就是每次在state变化后,执行监听的函数,其实就是个发布订阅,那么接下来我们就实现一下

function createStore(reducer,initialState){
    // 初始化订阅者数组
    const listener = [];
    // 执行订阅方法
    function subscribe(func) {
      // 存储订阅者函数  
     listener.push(func);
    }
    // 触发state变化

    function dispatch(action) {
        state = reducer(state, action);
        for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i];
            // 执行
            listener();
        }
    }   
}

写完以后,我们就可以在我们的index.html中使用了

<body>
      <button id="increment">我是周杰伦,我在油管新发布了一首歌</button>
      <div>我是粉丝小王,油管通知我,我现在只能看到周杰伦的<span id="state">0</span>首歌</div>
      <button id="decrement">我是周杰伦,我在油管删除了一首歌</button>
</body>
<script src="./MyRedux1.js"></script>
<script>
    const initState = 0;
    function reducer(state = initState, action) {
        switch (action.type) {
            case "increment":
                return state + 1;
            case "decrement":
                return state - 1;
            default:
                return state;
        }
    }
    const store = createStore(reducer, 0);
    const incrementBtn = document.getElementById("increment");
    const decrementBtn = document.getElementById("decrement");
    const state = document.getElementById("state");
    incrementBtn.onclick = function () {
        store.dispatch({ type: "increment" });
    };
    decrementBtn.onclick = function () {
        store.dispatch({ type: "decrement" });
    };
    store.subscribe(function () {
         state.innerHTML = store.getState();
    });
</script>

finally:

希望自己写的文章对大家有帮助,帮助大家提升修为,早日化神大圆满,😝