狀態管理庫,集中式存儲狀態,管理狀態
? redux
//簡單實現 redux源碼
export function createStore(reducer) {// reducer由用戶編寫, 必須是一個函數,dispatch的時候,reducer要執行if (typeof reducer !== 'function') throw new Error('reducer必須是一個函數')let state // 公共狀態let listeners = [] //事件池// 1.獲取公共狀態 store.getState()const getState = () => {return state}// 2.組件更新方法 進入事件池const subscribe = listener => {if (typeof listener !== 'function') throw new Error('listener 必須是一個函數')// 去重,防止重復添加if (!listeners.includes(listener)) {// 讓組件更新的方法進入事件池listeners.push(listener)}// 返回一個函數,用于從事件池中刪除對應的方法 (取消訂閱)return function unsubscribe() {// 找到事件池中的對應方法,并刪除listeners = listeners.filter(l => l !== listener)}}// 3.派發任務通知 Reducer 執行const dispatch = action => {// 必須是對象if (typeof action !== 'object') throw new Error('action 必須是一個對象')// 必須有 type 屬性if (typeof action.type === 'undefined') throw new Error('action 必須有 type 屬性')// 執行 reducer,參數為當前 state 和 action動作,返回新的 stateconst nextState = reducer(state, action)// 更新 公共狀態state = nextState// 通知事件池中的方法,更新組件for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener()}}// 4.redux初始化statedispatch({ type: '@@redux/INIT' })// 5. 返回 store 對象 { getState, dispatch, subscribe }return {getState,dispatch,subscribe}
}
1. 重要概念
狀態是只讀的,
后續修改狀態的方式是通過Reducer(純函數)接收舊狀態和 Action,返回新狀態。
Reducer無副作用,同樣的輸入必然得到同樣的輸出。
2. 數據流
- 組件通過 dispatch(action) 發送 Action。
- Redux 調用對應的 Reducer,生成新狀態。
- Store 更新狀態,通知所有訂閱了 Store 的組件。
- 組件通過 getState() 獲取新狀態并重新渲染。
問:Store 更新狀態,如何通知組件更新?
通過Store內部的Subscription(listener)
方法,listener 通常是 ?觸發組件重新渲染的函數,或是 ?與組件更新相關的副作用邏輯。見下:
- 類組件:listener 通常是調用 this.forceUpdate() 的函數,強制組件重新渲染。
class Counter extends React.Component {componentDidMount() {// 定義 listener:強制組件重新渲染this.unsubscribe = store.subscribe(() => {this.forceUpdate();});}componentWillUnmount() {this.unsubscribe(); // 取消訂閱}render() {const count = store.getState().count;return <div>{count}</div>;}
}
- 函數組件:listener 是 useState 的 副作用函數
import React, { useState, useEffect } from 'react';
import store from './store';function Counter() {// 定義forceUpdate 單純是為了觸發組件渲染更新,屬性無意義 const [_, forceUpdate] = useState(0); useEffect(() => {// 定義 listener:強制組件重新渲染const unsubscribe = store.subscribe(() => {forceUpdate(prev => prev + 1);});return unsubscribe; // useEffect 清理函數中取消訂閱}, []);const count = store.getState().count;return <div>{count}</div>;
}
- 使用 react-redux 的
connect
: connect 高階組件內部會處理訂閱邏輯,listener 是?比較新舊 props 并決定是否更新組件的函數。
connect 是 高階函數,內部會將公共狀態以props的方式傳遞給組件。
import { connect } from 'react-redux';class Counter extends React.Component {render() {return <div>{this.props.count}</div>;}
}// 映射狀態到 props
const mapStateToProps = (state) => ({count: state.count,
});// connect 內部邏輯(簡化):
// 1. Store的監聽器會比較新舊 mapStateToProps 的結果。
// 2. 若結果變化,觸發組件更新。
export default connect(mapStateToProps)(Counter);
- react-redux 的 useSelector鉤子
useSelector 的 listener 是 ?檢查選擇器返回值是否變化,并觸發重新渲染 的函數。
import { useSelector } from 'react-redux';function Counter() {// 內部實現(簡化):// 1. 訂閱 Store,監聽器會比較新舊 selector(state) 的結果。// 2. 若結果變化,觸發組件重新渲染。const count = useSelector((state) => state.count);return <div>{count}</div>;
}
?????? React-Redux
對redux 進行了包裝。
<Provider>
組件:Redux Store 注入整個 React 應用,使所有子組件可以訪問 Store。usSelector
Hook:從 Store 中獲取狀態,并自動訂閱狀態更新,當狀態變化時,若返回的值與之前不同,組件會重新渲染。useDispatch
Hook:獲取 dispatch 方法,派發 Actionconnect
高階組件(類組件兼容):。
import { connect } from 'react-redux';
import { increment, decrement } from './actions';class Counter extends React.Component {render() {const { count, increment, decrement } = this.props;return (<div><button onClick={decrement}>-</button><span>{count}</span><button onClick={increment}>+</button></div>);}
}// 映射 State 到 Counter的 props,以供取值
const mapStateToProps = (state) => ({count: state.count,
});// 映射 dispatch 到 Counter的 props,以供調用
const mapDispatchToProps = {increment,decrement,
};export default connect(mapStateToProps, mapDispatchToProps)(Counter);
connect :同 useSelector作用一樣,為了訪問 redux的store
但是這個比較繞一點(兼容了類組件)
- 輸入:一個 React 組件(類 or FC)
- 輸出:一個新的組件,該組件能訪問 Redux store
// 模擬 react-redux 的 connect 函數
const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {return class Connect extends React.Component {static contextType = ReactReduxContext; // 從 Provider 獲取 storeconstructor(props, context) {super(props);this.store = context.store;this.state = {mappedProps: this.calculateProps()};}componentDidMount() {this.unsubscribe = this.store.subscribe(() => {const newMappedProps = this.calculateProps();// 淺比較優化,避免不必要的渲染if (!shallowEqual(this.state.mappedProps, newMappedProps)) {this.setState({ mappedProps: newMappedProps });}});}componentWillUnmount() {this.unsubscribe();}calculateProps() {const state = this.store.getState();const stateProps = mapStateToProps(state);const dispatchProps = mapDispatchToProps(this.store.dispatch);return { ...stateProps, ...dispatchProps };}render() {return <WrappedComponent {...this.props} {...this.state.mappedProps} />;}};
};
combineReducers
組合多個獨立 reducer 的核心工具函數
- 自動分發 Action:
當一個 action 被 dispatch 時,combineReducers 會 ?自動將該 action 傳遞給所有子 reducer
import { combineReducers } from 'redux'import FatherAndSonReducer from './FatherAndSonReducer'
import TaskReducer from './TaskReducer'const rootReducer = combineReducers({FatherAndSonReducer,TaskReducer
})export default rootReducer