不能因為別人懷疑自己,但是可以通過別人啟發自己 !
昨天有人讓我把他當小白講講redux,我表示理出來的邏輯並不是很明確,他可能是在教我如何寫文章吧,我對自己寫的東西,並不是很負責,目的一直停留在增強自己的短時間記憶,我會安排時間將之前的文章做邏輯性梳理,當然先從這篇開始。
我們知道react元件傳遞是單向的,如果元件關係太遠,或者沒有關係,我們就會很麻煩,redux就是解決這個問題,他將資料儲存到倉庫,通過reducer派發action動作,來substrict
redux組成有5個
- createStore 建立倉庫,接受reducer作為引數
- bindActionCreator 繫結store.dispatch和action 的關係
- combineReducers 合併多個reducers
- applyMiddleware 洋蔥模型的中介軟體,介於dispatch和action之間,重寫dispatch
- compose 整合多箇中介軟體
createStore
主要匯出getState,dispatch,subscribe三個方法,分別是獲取倉庫,派發動作和訂閱事件。生成store,他包含所有資料,拿資料通過getState獲取, 一個 State 對應一個 View。只要 State 相同,View 就相同。
其內部儲存啦一顆狀態樹,這個狀態樹是任意型別的,當獲取狀態樹的時候後會複製一份狀態樹嗎,並匯出,防止對狀態樹的惡意更改。
因此,只能通過派發dispatch更改狀態,dispatch會呼叫reducer處理action 動作,reducer是個函式,他有state和action兩個引數,作用是將老得state經過action處理後返回新的state,action是個有type屬性的物件,來標示他對哪個狀態進行改變
於此同時,當狀態發生改變會觸發subscribe訂閱的監聽事件
export default function createStore(reducer,initState,enchancer) {
if (enchancer) {
return enchancer(createStore)(reducer,initState);
}
//倉庫內部儲存了一顆狀態樹。可以是任意型別
let state;
let listeners=[];
function getState() {
return JSON.parse(JSON.stringify(state));
}
//元件可以派發動作給倉庫
function dispatch(action) {
//呼叫reducer進行處理,獲取老的state,計算出新的state
state=reducer(state,action);
//通知其他的元件
listeners.forEach(l=>l());
}
//如果說其他的元件需要訂閱狀態變化時間的話,
function subscribe(listener) {
listeners.push(listener);
return function () {
listeners = listeners.filter(item=>item!==listener);
}
}
dispatch({type:'@@INIT'});
return {
getState,
dispatch,
subscribe
}
}
複製程式碼
bindActionCreator
雖然我們用dispatch派發事件,派發動作型別大概是一個有type屬性的物件,我們將這個物件的方法封一個actions的資料夾,在dispach的時候直接呼叫action,列入將dispatch(()=>{type:add})
,此時我們需要建立dispatch和actions的關係
<button onClick={()=>bindActions.add1(1)}>+</button>
export default function (actions,dispatch) {
//actions = {add(){return {type:ADD}},minus(){return {type:'MINUS'}}}
return Object.keys(actions).reduce(
(memo,key) => {
memo[key]=(...args) => dispatch(actions[key](...args));
return memo;
},{});
}
複製程式碼
combineReducers
返回一個函式,這個函式代表合併後的reducer,那麼他一定有兩個引數,最終返回新狀態,我們迭代reducers每一個屬性
//因為redux應用只能有一個倉庫,只能有一個reducer
//把多個reducer函式合併成一個
export default function (reducers) {
//返回的這個函式就是合併後的reducer
return function (state={},action) {
let newState={};
for (let key in reducers) {
// key=counter1
//reducers[counter1]=counter那個reducer
//state合併後的狀態樹
//if (key===action.name) {
newState[key]=reducers[key](state[key],action);//state[key]老狀態
//}
}
return newState;
}
}
複製程式碼
我們可以發現,每個元件都需要獲取倉庫,管理訂閱,向倉庫派發事件,這樣很融於,我們需要複用這樣的邏輯,可以用高階元件,或者將函式作為子元件,我們此時用這個庫
applyMiddleware
中介軟體大概是redux裡面比較難理解的部分啦
他的原理如上圖,state和dispatch構成啦我們的倉庫,我們可以向倉庫傳送action通過reducer改變狀態,狀態改變之後可以修改檢視,使用者可以通過滑鼠點選檢視,檢視派發action,改變狀態,形成迴圈,,有時候我們需要釋出非同步操作,想在派發前,派發後做一些額外動作,此時我們就需要插入中介軟體,我們的方法就是得到dispatch方法,重寫dispacth,這裡用到啦我們說的原始碼解析
一個redux中介軟體大概就是如下,之後可能會將redux和express和koa中介軟體做個對比給大家總結一下
function logger(store){//getState ,新的dispatch
return function(next){//store.dispatch舊的
return function action(){
console.log('old');
next()
console.log('new')
}
}
}
//解析過程
let logger = store => next =>action =>{
}
//以下是處理過程
function applyMiddleware(middleware){
return function(creacteStore){
return function(reducer){
let store = creacteStore(reducer);
let middleware2 = middleware(store)
let dispatch = middleware2(store.dispatch)
}
}
}
let store = applyMiddleware(logger)(creacteStore)(reducer)
複製程式碼
import compose from './compose';
export default function (...middlewares) {
return function (createStore) {
return function (reducers) {
let store=createStore(reducers);//這就是原始的倉庫 dispatch 就是原始的dispatch
let dispatch;//dispatch方法指向新的dispatch方法
let middlewares2 = middlewares.map(middleware=>middleware({
getState: store.getState,
dispatch:action=>dispatch(action)
}));//呼叫第一層去掉
dispatch=compose(...middlewares2)(store.dispatch);//再呼叫第二次把第二層去掉
return {
...store,
dispatch
}
}
}
}
複製程式碼
compose整合中介軟體
function add1(str) {
return 1+str;
}
function add2(str) {
return 2+str;
}
function sum(a,b) {
return a+b;
}
//let ret=add1(add2(add3('zdl')));
//console.log(ret);
function compose1(...fns) {//[add1,add2,add3]
return function (...args) {
let last=fns.pop();
return fns.reduceRight((val,fn)=>fn(val),last(...args));
}
}
export default function(...fns) {
return fns.reduce((a,b)=>(...args)=>a(b(...args)));
}
/**
* 第一次的時候 a =add1 b=add3 let ret = add1(add2(...args))
* 第二次的時候 (...args)=>add1(add2(sum(...args)))
*/
let ret=compose(add1,add2,sum)('a','b');
console.log(ret);//123zdl
複製程式碼
react-redux
有四個檔案
- connect 將store和dispatch分別對映成props屬性物件,返回元件
- context 上下文 匯出Provider,,和 consumer
- Provider 一個接受store的元件,通過context api傳遞給所有子元件,優點類似於路由啦
- index
index
import Provider from './Provider';
import connect from './connect';
export {
Provider,
connect
}
複製程式碼
Provider
/**
* 是一個元件,用來接受store,再經過它的手通過context api傳遞給所有的子元件
*/
import React,{Component} from 'react'
import {Provider as StoreProvider} from './context';
import PropTypes from 'prop-types';
export default class Provider extends Component{
//規定如果有人想使用這個元件,必須提供一個redux倉庫屬性
static propTypes={
store:PropTypes.object.isRequired
}
render() {
let value={store:this.props.store};
return (
<StoreProvider value={value}>
{this.props.children}
</StoreProvider>
)
}
}
複製程式碼
context
import React from 'react'
let {Provider,Consumer}=React.createContext();
export {Provider,Consumer};
複製程式碼
connect
這是個高階函式,分別傳入兩個方法mapStateToProps,mapDispatchToProps,將store和dispatch分別對映成props屬性物件,這樣在頁面就不需要飲用倉庫,也不需要繫結action和dispatch,也不需要訂閱狀態,返回元件
import {Consumer} from './context';
import React,{Component} from 'react';
import {bindActionCreators} from '../redux';
/**
* connect實現的是倉庫和元件的連線
* mapStateToProps 是一個函式 把狀態對映為一個屬性物件
* mapDispatchToProps 也是一個函式 把dispatch方法對映為一個屬性物件
*/
export default function (mapStateToProps,mapDispatchToProps) {
return function (Com) {
//在這個元件裡實現倉庫和元件的連線
class Proxy extends Component{
state=mapStateToProps(this.props.store.getState())
componentDidMount() {
this.unsubscribe = this.props.store.subscribe(() => {
this.setState(mapStateToProps(this.props.store.getState()));
});
}
componentWillUnmount = () => {
this.unsubscribe();
}
render() {
let actions={};
//如果說mapDispatchToProps是一個函式,執行後得到屬性物件
if (typeof mapDispatchToProps === 'function') {
actions = mapDispatchToProps(this.props.store.dispatch);
//如果說mapDispatchToProps是一個物件的話,我們需要手工繫結
} else {
actions=bindActionCreators(mapDispatchToProps,this.props.store.dispatch);
}
return <Com {...this.state} {...actions}/>
}
}
return () => (
<Consumer>
{
value => <Proxy store={value.store}/>
}
</Consumer>
);
}
}
複製程式碼