随着单页开发日趋复杂,管理不断变化的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赋值给旧的statefunction 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)复制代码