从基础开始深入,一步步的实现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")
})