本文會逐步探索redux到應用分爲3個系列
作用
它幫助我們連接UI層和數據層。即幫我們合併一些重複操作即獲取狀態getState
、修改狀態dispatch
、訂閱更新subscribe
。因此提供了兩個核心API:
Provider
: 接收 store,並將其掛載到context上,目的是爲了後代組件獲取狀態Connect
: 將 state、props傳遞組件並自動訂閱更新
核心實現
本文就核心兩個API進行分析如下問題:
- 子代元素如何獲取 store?
- store變化如何更新視圖?
- 如何做到組件props增強?
- 怎麼做到只訂閱關心的數據,與防止重複渲染?
下面我們根據實現這兩個核心功能來逐步揭曉上面的問題。
Provider
- 使用 context 傳遞 store
- 註冊監聽組件更新函數(即調用
trySubscribe
) - 這裏可以對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
- 接收
mapStateToProps
、mapDispatchToProps
方法對包裹組件props
增強 - 註冊頁面更新函數
onStateChange
,目的爲了頁面重新渲。 - 頁面更新後重新計算
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} />
}
}
反思
- connect中用到了什麼設計模式?這種模式的好處
connect,與我們熟悉的HOC都是用到了裝飾器模式
;這種模式可以對組件功能擴展而不必更改原有邏輯(即符合開閉原則)
待研究
- Provider中的 store 對比更新操作實現?
- 其它相關API實現與其它相關hooks?
本文之後還會繼續更新…