Redux源碼學習篇(二) -- react-redux實現

本文會逐步探索redux到應用分爲3個系列

  1. redux 實現
  2. react-redux 實現
  3. redux中間件的應用與實現

作用

它幫助我們連接UI層和數據層。即幫我們合併一些重複操作即獲取狀態getState、修改狀態dispatch、訂閱更新subscribe。因此提供了兩個核心API:

  • Provider: 接收 store,並將其掛載到context上,目的是爲了後代組件獲取狀態
  • Connect: 將 state、props傳遞組件並自動訂閱更新

核心實現

本文就核心兩個API進行分析如下問題:

  1. 子代元素如何獲取 store?
  2. store變化如何更新視圖?
  3. 如何做到組件props增強?
  4. 怎麼做到只訂閱關心的數據,與防止重複渲染?

下面我們根據實現這兩個核心功能來逐步揭曉上面的問題。

Provider

  1. 使用 context 傳遞 store
  2. 註冊監聽組件更新函數(即調用 trySubscribe)
  3. 這裏可以對state對比然後判斷是否要更新組件(本文未做)
// Context.ts
import React from 'react'

export const ReactReduxContext=React.createContext<ReturnType<any>>(null);

if(process.env.NODE_ENV!=='production'){
    ReactReduxContext.displayName='ReactRedux'
}
export default ReactReduxContext

這裏只是簡單的採用觀察者模式,源碼中採用的發佈訂閱模式。

// Subscription.ts
export default class Subscription<S>{
    private store:S|any;
    private listeners:any[]|null;
    public onStateChange:Function | null | undefined;
    private unsubscribe:null|any;
    constructor(store:S){
        this.store=store;
        this.unsubscribe=null
        this.listeners=[this.handleChangeWrapper];
    }

    //需要組件中設置用來更新組件
    handleChangeWrapper=()=>{
        if(this.onStateChange){
            this.onStateChange()
        }
    }

    //註冊監聽
    addListener(listener:any){
        this.listeners!.push(listener)
    }

    //通知更新
    notify=()=>{
        this.listeners!.forEach(listener=>{
            listener()
        })
    }

    //監聽store
    trySubscribe(){
        if(!this.unsubscribe){
            this.unsubscribe=this.store.subscribe(this.notify)
        }
    }    
    //取消訂閱
    tryUnsubscribe(){
        if(this.unsubscribe){
            this.unsubscribe();
            this.unsubscribe=null;
            this.listeners=null
        }
    }
}
// Provider.ts 
import React, { useMemo, useEffect } from 'react'
import Subscription from './Subscription'
import { ReactReduxContext } from './Context'
interface IProps {
    store: any;
    context?: React.Context<any>;
    children: any;
}

export default ({ store, context, children }: IProps) => {
    const contextValue = useMemo(() => {
        const subscription = new Subscription(store);
        return {
            store,
            subscription
        }
    }, [store]);
    const previousState = useMemo(() => store.getState(), [store]);
   
    useEffect(() => {
        const { subscription } = contextValue;
        //將 註冊組件更新函數(即下文中的onStateChange)
        subscription.trySubscribe();

        // if(previousState!==store.getState()){
        //    subscription.not
        // }
        return () => {
            subscription.tryUnsubscribe();
            subscription.onStateChange = null;
        }
    }, [contextValue, previousState])

    const Context = context || ReactReduxContext
    return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

Connect

  1. 接收 mapStateToPropsmapDispatchToProps方法對包裹組件props增強
  2. 註冊頁面更新函數 onStateChange,目的爲了頁面重新渲。
  3. 頁面更新後重新計算 props
//Connect.tsx
import React, { useContext, useMemo, useState, useEffect } from 'react'
import { ReactReduxContext } from './Context'
export default (mapStateToProps: Function, mapDisPatchToProps: Function) =>
    (WrappedComponent: React.ComponentProps<any>) => {
        return (props: any) => {
            const { store, subscription } = useContext<any>(ReactReduxContext);
            //設置狀態更新state,爲了驅動組件更新(newProps更新)
            const [state,setState]=useState(0);
            
            useEffect(()=>{
                    subscription.onStateChange=()=>setState(state+1);
            },[state])

            const newProps = useMemo(() => {
                const stateProps = mapStateToProps(store.getState());
                const dispatchProps = mapDisPatchToProps(store.dispatch);
                return {
                    ...stateProps,
                    ...dispatchProps,
                    ...props
                }
            }, [props,state, store])

            return <WrappedComponent {...newProps} />
        }
    }

反思

  1. connect中用到了什麼設計模式?這種模式的好處
    connect,與我們熟悉的HOC都是用到了 裝飾器模式;這種模式可以對組件功能擴展而不必更改原有邏輯(即符合開閉原則)

待研究

  • Provider中的 store 對比更新操作實現?
  • 其它相關API實現與其它相關hooks?

本文之後還會繼續更新…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章