从0到1实现redux
/ / 点击 / 阅读耗时 9 分钟初学redux的时候主要是通过查看官方文档以及视频实战,虽然知道了redux的实际用法,但是也仅仅只是知道了API的用法,对于其为什么这样的设计及原理并不是很清楚。所以为了搞懂redux实现原理,本篇记录从0到1一步步如何实现redux。
redux核心
在实现redux之前,首先回顾下redux的几个核心以及具体使用方法:
- store:在redux中通过createStore函数创建store,可以把store看成一个容器,它有几个方法,包括获取、修改数据。
- state:store对象中的所有数据。
- action:一个描述对象,必须包含type属性。通常type是一个字符串用来描述数据的操作。
- reducer:在redux中改变数据是通过store接受一个action后返回新的state,从接收action到计算出新的state过程称为reducer。
从0到1实现redux
redux1.0
根据官方文档可以发现,通过createStore函数初始化赋值给store后,可以通过store.dispatch(),store.getState()等方法修改/获取数据,那么我们可以在createStore函数中定义这些函数并返回即可。
let initData = {
count:1
}
let createStore = function(initState){
let state = initState;
let listeners = []
//订阅
function subscribe(listener){
listeners.push(listener)
}
// 发布
function dispatch(newState){
//获取新数据
state = newState;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener()
}
}
function getState(){
return state;
}
return {
subscribe,
dispatch,
getState
}
}
let store = createStore(initData);
// 订阅
store.subscribe(()=>{
let state = store.getState();
console.log('state 的值是:',state); //state 的值是 2
})
//通过dispatch派发更改数据
store.dispatch({
...store.getState(),
count:2
})
以上代码首先通过创建一个createStore函数传入初始数据,函数内部定义state以及订阅容器变量。然后再分别创建订阅、发布、获取state函数,最后返回这三个函数。使用方法就是执行createStore传入initData,然后执行订阅。最后想要改变数据的时候通过dispatch触发数据更新。
当然以上仅是一个简易版数据更改操作,并未涉及到action和reducer,后续会再继续完善。
redux2.0(实现action和reducer)
根据redux1.0代码可以发现数据是可以按照我们的要求改变,但是会遇到两个问题。首先就是我们对修改数据没有任何约束,它可以被任何人修改成任何属性。我们无法数据进行自定义修改。其次,根据官方文档修改数据的方法是通过dispatch()一个带有type属性的对象。所以根据这两点,再重新改写下。
按照以上思路首先改写createStore函数中的dispatch函数,从原来的传入新数据改为传入action对象,且state也不能像之前一样赋值给传入的newState:
let createStore = function(initState){
let state = initState;
//...省略
function dispatch(action){
state = reducer(state,action) //由于传入的是一个action对象,可以创建一个reducer的函数转换成数据
}
}
由于dispatch改为接收一个action对象,所以我们需要创建reducer函数将传入的对象转换成数据:
function reducer(state,action){
switch (action.type){
case 'INCREMENT':
return {
...state,
count:state.count + 1
}
case 'DECREMENT':
return {
...state,
count:state.count - 1
}
default:
return state;
}
}
创建好reducer之后,需要把这个数据转换函数传递给createStore,结合下就是:
let initState = {
count: 0
}
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1
}
case 'DECREMENT':
return {
...state,
count: state.count - 1
}
default:
return state;
}
}
const createStore = function (reducer, initState) {
let state = initState;
let listeners = [];
function subscribe(listener) {
listeners.push(listener)
}
function dispatch(action) {
state = reducer(state, action);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener()
}
}
function getState() {
return state;
}
return {
subscribe,
dispatch,
getState
}
}
然后我们就可以根据自定义的type来改变数据:
let store = createStore(reducer,initState)
store.subscribe(() => {
let state = store.getState()
console.log(`count的值是:${state.count}`);
})
//递增
store.dispatch({
type:'INCREMENT'
})
//递减
store.dispatch({
type:'DECREMENT'
})
//无效修改
store.dispatch({
count:'aaa'
})
redux3.0(实现combineReducers)
根据redux2.0我们可以自定义实现数据的更改,但是会发现一个问题。即在上面代码中,我们的initState只有一条数据,如果数据量增加,把所有的action写在一个reducer里面会十分庞大复杂。所以我们需要根据state拆分成多个对应的reducer函数,然后通过一个函数(combineReducers)整合。
首先初始化state,里面包含两个数据:
let initState = {
counter:{
count:0
},
info:{
name:'zs',
desc:'redux从0-1'
}
}
然后分别定义他们的reducer:
function counterReducer(state,action){
switch(action.type){
case 'INCREMENT':
return {
...state,
count:state.count + 1
}
case 'DECREMENT':
return {
..state,
count:state.count -1
}
default:
return state;
}
}
function infoReducer(state,action){
switch(action.type){
case 'SET_NAME':
return {
...state,
name:action.name
}
case 'SET_DESC':
return {
...state,
desc:action.desc
}
}
}
定义好reducer之后就是实现combineReducer,实现combineReducers函数的思路就是:遍历所有的reducer,执行完成之后整合成一个新的state。
function combineReducer(reducers){
const reducerKeys = Object.keys(reducers)
return function combineAction(state = {},action){
//生产合并新的state
const newState ={}
//遍历所有的reducer,最终返回一个合并后的state
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i];
const reducer = reducers[key];
// 获取之前key的state
const previousStateForKey = state[key];
//执行每个reducer函数,获取最新的state
const nextStateForKey = reducer(previousStateForKey,action);
newState[key] = nextStateForKey;
}
return newState;
}
}
然后来使用下combineReducer:
let initState = {
counter:{
count:0
},
info:{
name:'zs',
desc:'redux从0-1'
}
}
const reducer = combineReducer({
counter: counterReducer,
info: InfoReducer
})
let store = createStore(reducer, initState);
store.subscribe(() => {
let state = store.getState();
console.log(state.counter.count, state.info.name, state.info.description);
});
store.dispatch({
type:'INCREMENT'
})
store.dispatch({
type:'SET_NAME',
name:'ls'
})