redux 流程演绎

131 阅读5分钟

从基础开始深入,一步步的实现redux

初始需求是完成对统一状态的数据的修改,想要达到的目的是修改状态数据,即可完成对页面显示数据的修改,但是直接对公共数据的修改是不安全的

所以可以得出以下2个要点:

  • 1.统一管理的状态数据是不能直接修改的
  • 2.需要提供一个数据来完成对状态数据的修改,其他调用这个方法就可以

那要实现这个统一修改状态数据的方法有哪些要注意的地方呢?

  • 1.需要告知这个函数修改的目标数据
  • 2.需要告知函数要将目标数据修改成如何

所以需要我们设计或者定义个方法,这个方法有以下的要求:

  • 1.参数需要传递个对象
  • 2.参数对象中必须要有个属性为type,表示修改某个目标数据的标志
  • 3.参数对象的其他属性就是要将目标对象修改后的值
  • 4.方法的主要逻辑是根据不同type属性值来完成对状态数据的修改
  • 5.对状态数据的修改时要注意修改之后返回的对象为新对象
return state = { ...state, titleState: { ...state.titleState, text: action.text } }

注意: 在完成这个方法的调用之后,对数据更新完成后,需要重新调用render函数对数据进行渲染

保护状态数据不被修改

到目前为止,我们可以对状态数据进行修改了,但是有个问题是:

状态数据暴露在全局,这样很容易被修改

解决方案是:创建一个容器将状态放进去,这样状态数据就变成私有的,无法获取到

但是使用函数的方式的话,外界要使用状态数据和派发动作的方法就无法获取到

  • 1.dispatch方法需要获取到状态数据,所以也放置到这个函数中,但是外面无法使用dispatch方法,需要将dispatch方法外放出去
  • 2.状态数据对象在函数外也使用到了,但是并不能直接将这个对象给外界使用,不然就没有任何意义了,所以可以克隆一个状态数据对象给外界使用
克隆方式:
let getState = () => JSON.parse(JSON.stringify(state));

状态数据由外界定义

功能继续优化,因为之前的做法有2个缺陷:

  • 缺陷1:状态数据已经是私有的,但是这样做是不合理的,因为状态数据肯定不是函数自身就定义好了的,而是由函数外定义
  • 缺陷2:创建容器中的派发动作的方法里面具体的type属性的参数也不应该是函数自身来定义的,而是由外界来完成的,因为框架作为通用的是不可能定义这样具体的type类型

针对这2个缺陷,我们需要做如下的操作:

  • 1.状态数据在函数外定义 initState
  • 2.派发动作函数中的switch判断type交由外界函数处理reducer
  • 3.创建容器时需要将这个reducer处理函数传到容器中,因为容器中的派发动作函数需要使用到这个reducer处理函数
function createStore(reducer) {
    let state;
    function dispatch(action) {
        state = reducer(state, action);
    }
    dispatch({});
    let getState = () => JSON.parse(JSON.stringify(state));
    return {
        dispatch, getState
    }
}
  • 4.reducer处理函数传递2个参数,在创建容器时状态对象state为undefined,此时就会出错,因此需要给reducer处理函数的state状态函数对象传递一个初始值
reducer(state = initState, action)
  • 5.注意:为了state状态对象在处理的时候不为undefined,需要在创建容器函数中调用一次dispatch派发动作函数,并传递一个空对象,这样,容器中的状态对象就会被赋值成外界的initState状态对象

事件发布订阅和取消事件订阅

由于在事件完成派发操作之后都需要做一次重新渲染页面的操作,所以这里采用事件发布订阅的方式来完成这种后置回调

容器函数中创建一个事件订阅函数,用数组将订阅的函数存储起来,在动作派发完成之后遍历数组,完成事件发布

// 事件订阅
let subscribe = (fn) => {
    listeners = [...listeners, fn];
}
//事件发布
function dispatch(action) {
    state = reducer(state, action);
    // 依次让订阅的函数执行
    listeners.forEach(item => item());
}

同时要有一个取消事件订阅的函数.

  • 1.在事件订阅函数中,让事件订阅函数返回一个函数
  • 2.在返回的函数中进行取消事件订阅的处理
  • 3.当调用这个函数的时候,则在数组中删除掉订阅的这个函数
// 事件订阅
let subscribe = (fn) => {
    listeners = [...listeners, fn];
    // 返回一个函数,用于取消事件订阅
    return () => {
        listeners = listeners.filter(item => item != fn);
    }
}

事件订阅和取消订阅的使用:

let unSubscribe = store.subscribe(() => {
    console.log("render done");
});
unSubscribe();

redux 基础库

redux基础库的实现如下:

// redux 状态容器
function createStore(reducer) {                                                                                     
    // 定义状态和订阅数组
    let state, listeners = [];
    // 动作派发
    let dispatch = (action) => {
        state = reducer(state, action);
        listeners.forEach(item => item());
    }
    // 初始化状态数据
    dispatch({});
    // 克隆状态对象
    let getState = () => JSON.parse(JSON.stringify(state));

    // 事件订阅
    let subscribe = (fn) => {
        listeners = [...listeners, fn];
        return () => {
            listeners = listeners.filter(item => item != fn);
        }
    }
    return {
        getState,
        dispatch,
        subscribe
    }
}

redux 库使用

redux库在原生中的使用步骤:

  • 1.定义功能类型常量
const ADD = "add";
const MINUS = "minus";
  • 2.定义初始状态,创建reducer函数时将初始状态赋给reducer函数参数state的默认值
let initState = { num: 0 };
function reducer(state = initState, action) {
    switch (action.type) {
        case ADD:
            return { ...state, num: state.num + action.count }
            break;
        case MINUS:
            return { ...state, num: state.num - action.count };
            break;
        default:
            return state;
    }
}
  • 3.创建状态容器,将reducer作为参数传进去
let store = createStore(reducer);
  • 4.默认渲染一次,在react的render方法中已经做了
function render() {
    window.number.innerHTML = store.getState().num;
}
render();
  • 5。派发功能动作,修改状态
window.add.addEventListener("click", function () {
    store.dispatch({ type: ADD, count: 3 });
});

window.minus.addEventListener("click", function () {
    store.dispatch({ type: MINUS, count: 2 });
});
  • 6.派发完成动作后要订阅事件,重新渲染页面
store.subscribe(render);
store.subscribe(() => {
    console.log("render")
})