redux简单实现与分析

1,924 阅读5分钟

随着单页开发日趋复杂,管理不断变化的state非常困难。在React中,数据在组件中是单向流动的。数据从一个方向父组件流向子组件(通过props),但是,两个非父子关系的组件(或者称作兄弟组件)之间的通信就比较麻烦(可以通过父给子传递方法,子通过这个方法修改父的数据,后父传给另一个子组件来实现)。Redux的出现就是为了解决state里的数据问题

1.Redux概念解析

1.1 Store

  • Store就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个Store
  • Redux 提供createStore这个函数,用来生成Store
import { createStore } from 'redux';
const store = createStore(fn);
//createStore函数接受另一个函数作为参数,返回新生成的Store对象。

1.2 State

Store对象包含所有数据。如果想得到某个时点的数据,就要对Store生成快照。这种时间点的数据集合,就叫做State。 当前时刻的State,可以通过store.getState()拿到。

import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();

Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。

1.3 Action

State的变化,会导致View的变化。但是,用户接触不到 State,只能接触到View 。Action 就是 View 发出的通知,表示State 应该怎样变化,它会运送数据到 Store。 Action是一个对象。其中的type属性是必须的,表示 Action 的名称。

const action = {
  type: 'ADD_TODO',
  payload: '学习redux'
};

1.4 Action Creator

View要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。

const ADD_TODO = '添加 TODO';
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('学习Redux');

上面代码中,addTodo函数就是一个 Action Creator。

1.5 store.dispatch()

store.dispatch()是 View 发出 Action 的唯一方法。

import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: '学习Redux'
});

上面代码中,store.dispatch接受一个 Action 对象作为参数,将它发送出去。 结合 Action Creator,这段代码可以改写如下。

store.dispatch(addTodo('学习Redux'))

1.6 Reducer

Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。 这种 State 的计算过程就叫做 Reducer。 Reducer 是一个纯函数,它接受 当前 State 和Action作为参数,返回一个新的 State。

const reducer = function (state, action) {
  return new_state;
};

2.redux 简单实现

知道了上面redux的基本概念,我们来实现一个简单的redux,相信你可以更了解redux。

2.1简单起步

function createStore(reducer) {
   
}
let store=createStore(reducer);

reducer是我们定的,我们想让state怎样改,reducer中这是,这是我们自己写的。为什么传入的是reducer?因为createStore是创建state的集合store的,而reducer

Reducer 是一个纯函数,它接受 当前 State 和Action作为参数,返回一个新的 State。

所以createStore内部是通过Reducer返回的state。

2.2 将reducer中的state覆盖createStore的state

reducer返回一个新的state。我们怎样将返回的state覆盖store中对应的state呢?state不能直接修改,只能通过dispatch派发action让reducer修改
如果一不小心state='',则state将被清空,为了避免错误的修改。修改state只能通过dispatch。

dispatch不是发送action的吗?
对,dispatch内部是通过reducer使用action返回的新state赋值给旧的state

function createStore(reducer) {
    let state;//此时默认还是undefined
    function dispatch(action) {//派发
        state=reducer(state,action);
    }
    dispatch({});//目的是第一次时将默认状态覆盖掉自身状态
}
function reducer(state={title:'标题'},action) {
    switch (action.type){
       
    }
    return state;
}

2.3 getState

getState是唯一获得state的方法,但是getState获得的事原state的深拷贝对象,防止被篡改。

function createStore(reducer) {
    let state;
    function dispatch(action) {
        state=reducer(state,action);
    }
    dispatch({});
    let getState=()=>JSON.parse(JSON.stringify(state));
    return(getState,dispatch)
}
function reducer(state={title:'标题'},action) {
    switch (action.type){
       
    }
    return state;
}

2.4完善reducer

function createStore(reducer) {
    let state;
    function dispatch(action) {
        state=reducer(state,action);
    }
    dispatch({});
    let getState=()=>JSON.parse(JSON.stringify(state));
    return(getState,dispatch)
}

//以下需要用户自己写
const CHANGE_TITLE='change_title'
function reducer(state={title:'标题'},action) {
    switch (action.type){
        case CHANGE_TITLE://{type:CHANGE_TITLE,content:'xx'}
            return {...state,title:action.content}
    }
    return state;
}
let store=createStore(reducer);
function render() {
    document.querySelector('.title').innerHTML=store.getState().title
}
render();

2.5调用dispatch

setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
    render();
},2000)
setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
    render();
},2000)

现在我们调用了两次setTimeout,需要两次render方法。如果有多个,需要多次调用render。现在我们通过发布订阅将render,或其他的方法订阅。每次dispatch后都调用这些方法。

2.6发布订阅 subscribe

function createStore(reducer) {
    let state;
    function dispatch(action) {
        state=reducer(state,action);
        listeners.forEach(item=>item());
    }
    let listeners=[]//存放所有的监听函数
    let subscribe=(fn)=>{
        listeners.push(fn);
    }
    dispatch({});
    let getState=()=>JSON.parse(JSON.stringify(state));
    return(getState,dispatch,subscribe)
}

let store=createStore(reducer);
store.subscribe(render);//订阅render方法
//也可以是其他方法
store.subscribe(function(){
    alert(1111)
);
function render() {
    document.querySelector('.title').innerHTML=store.getState().title
}
render();
setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
},2000)
setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
},2000)

2.7 发布订阅 unSubscribe

上面方法中有一次我们订阅了alert(1111)的方法,如果我们想让第一次setTimeout运行这个方法,第二次就不需要了。这时候就需要unSubscribe
执行subscrib时,返回一个unSubscribe的方法

let unSubscribe=store.subscribe(function(){
    alert(1111)
);
function createStore(reducer) {
    let state;
    function dispatch(action) {
        state=reducer(state,action);
        listeners.forEach(item=>item());
    }
    let listeners=[]//存放所有的监听函数
    let subscribe=(fn)=>{
        listeners.push(fn);
        return ()=>{//取消绑定的函数,调用可以删除函数
            listeners=listeners.filter(item=>item!==fn);
        }
    }
    dispatch({});
    let getState=()=>JSON.parse(JSON.stringify(state));
    return(getState,dispatch,subscribe)
}
store.subscribe(render);
let unSubscribe=store.subscribe(function(){
    alert(1111)
);
function render() {
    document.querySelector('.title').innerHTML=store.getState().title
}
render();
setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
+    unSubscribe()
},2000)
setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
},2000)