一個項目,一個複雜的邏輯,我以爲狀態管理顯得尤其的重要,狀態管理的好很差,直接體現了一個項目的邏輯性、可讀性、維護性等是否清晰,易讀,和高效。javascript
從最先的類組件使用 this.state, this.setState 去管理狀態,到 redux , subscribe, dispatch 的發佈訂閱,redux 的使用就面臨重複和沉重的的 reducer,讓我儼然變成了 Ctrl CV 工程師。因而後面接觸 dva,它是一個基於 redux 和 redux-saga 的數據流方案。經過 model 來分片管理全局狀態,使用 connect 方法去給須要的深層次的組件傳遞狀態。html
到後面 react hooks 出來以後,業界也出了不少自身管理狀態的,基於 hooks 封裝,各個模塊都有一個基於本身 hooks 的狀態 store。確實很好的解決了函數組件的狀態管理,和模塊自身內部的狀態管理,可是仍是解決不了在全局組件中,層層傳遞的狀態依賴讓結構變得複雜,繁瑣的問題。不用任何的管理工具咱們如何作到跨組件通訊?java
不是說咱們不去用 dva 這樣的管理工具?我並非說 dva 很差用,而是我以爲有時候不必用。我以爲他過重了。react
讀完這邊文章,即便你以爲個人管理方式很差,你也能夠學習和了解到 useMemo, useContext,useImmer等。git
Context-React 官網介紹github
// Context 可讓咱們無須明確地傳遍每個組件,就能將值深刻傳遞進組件樹。
// 爲當前的 theme 建立一個 context(「light」爲默認值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一個 Provider 來將當前的 theme 傳遞給如下的組件樹。
// 不管多深,任何組件都能讀取這個值。
// 在這個例子中,咱們將 「dark」 做爲當前的值傳遞下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中間的組件不再必指明往下傳遞 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 讀取當前的 theme context。
// React 會往上找到最近的 theme Provider,而後使用它的值。
// 在這個例子中,當前的 theme 值爲 「dark」。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
複製代碼
const MyContext = React.createContext(defaultValue);
<MyContext.Provider value={/* 某個值 */}>
<App>
... 多層組件嵌套內有一個 Goods 組件
<Goods />
</App>
</MyContext.Provider >
// 某個 子子子子子組件 Goods
<MyContext.Consumer>
{value => /* 基於 context 值進行渲染*/}
</MyContext.Consumer>
複製代碼
app.jsnpm
import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
// State 也包含了更新函數,所以它會被傳遞進 context provider。
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}
render() {
// 整個 state 都被傳遞進 provider
return (
<ThemeContext.Provider value={this.state}> <Content /> </ThemeContext.Provider> ); } } function Content() { return ( <div> <ThemeTogglerButton /> </div> ); } ReactDOM.render(<App />, document.root); 複製代碼
// Theme context,默認的 theme 是 「light」 值
const ThemeContext = React.createContext('light');
// 用戶登陸 context
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// 提供初始 context 值的 App 組件
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// 一個組件可能會消費多個 context
function Content() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
複製代碼
./connect.js 文件redux
使用 connect 也是基於 react-redux 思想,把它封裝爲一個方法。調用 connect 方法返回的是一個高階組件。而且 connect 方法中支持傳入一個函數,來過濾,篩選子組件須要的狀態,也便於維護 從新 render 等數組
import React, { createContext } from 'react';
import { useImmer } from 'use-immer';
// useImmer 文章末尾有介紹推薦
const ctx = createContext();
const { Consumer, Provider } = ctx
const useModel = (initialState) => {
const [state, setState] = useImmer(initialState);
return [
state,
setState
];
}
const createProvider = () => {
function WrapProvider(props) {
const { children, value } = props;
const [_state, _dispatch] = useModel(value)
return (
<Provider value={{ _state, _dispatch, }}> {children} </Provider>
)
}
return WrapProvider
}
export const connect = fn => ComponentUi => () => {
return (
<Consumer> { state => { const {_state, _dispatch} = state const selectState = typeof fn === 'function' ? fn(_state) : _state; return <ComponentUi _state={selectState} _dispatch={_dispatch} /> } } </Consumer> ) }; export default createProvider; 複製代碼
import React from 'react';
import Header from './layout/Header.jsx';
import Footer from './layout/Footer.jsx';
import createProvider from './connect';
const Provider = createProvider()
const initValue = { user: 'xiaoming', age: 12 }
function App() {
return (
<Provider value={initValue}> <Header /> <Footer /> </Provider>
)
}
export default App;
複製代碼
Header.jsxbash
import React from 'react';
import { Select } from 'antd';
import { connect } from '../connect';
const { Option } = Select;
function Head({ _state: { user, age }, _dispatch }) {
return (
<div className="logo" > <Select defaultValue={user} value={user} onChange={(value) => { _dispatch(draft => { draft.user = value }) }}> <Option value='xiaoming'>小明</Option> <Option value='xiaohong'>小紅</Option> </Select> <span>年齡{age}</span> </div>
)
}
export default connect()(Head);
複製代碼
Footer.jsx
import React, { Fragment } from 'react';
import { Select } from 'antd';
import { connect } from '../../connect';
const { Option } = Select;
function Footer({ _state, _dispatch }) {
const { user, age } = _state;
return (
<Fragment> <p style={{marginTop: 40}}>用戶:{user}</p> <p>年齡{age}</p> <div> <span>改變用戶:</span> <Select defaultValue={user} value={user} onChange={(value) => { _dispatch(draft => { draft.user = value }) }}> <Option value='xiaoming'>小明</Option> <Option value='xiaohong'>小紅</Option> </Select></div> <div> <span>改變年齡:</span> <input onChange={(e) => { // 這裏使用 persist 緣由能夠看文章末尾推薦 e.persist(); _dispatch(draft => { draft.age = e.target.value }) }} /> </div> </Fragment> ) } export default connect()(Footer); 複製代碼
咱們都知道 react 16.8 之後也出了 useContext 那麼咱們能夠經過使用 useContext 來優化 connect 方法
// 未使用 useContext
export const connect = (fn) => (ComponentUi) => () => {
const state = useContext(ctx)
console.log(state);
return (
<Consumer>
{
state => {
const { _state, _dispatch } = state
const selectState = typeof fn === 'function' ? fn(_state) : _state;
return <ComponentUi _state={selectState} _dispatch={_dispatch} />
}
}
</Consumer>
)
};
// 使用 useContext
export const connect = fn => ComponentUi => () => {
const { _state, _dispatch } = useContext(ctx);
const selectState = typeof fn === 'function' ? fn(_state) : _state;
return <ComponentUi _state={selectState} _dispatch={_dispatch} />;
};
複製代碼
注意: 調用了 useContext
的組件總會在 context 值變化時從新渲染。若是重渲染組件的開銷較大,你能夠經過文章末尾推薦的沒必要要從新 render 開銷大的組件去了解如何優化。
4步代碼跑起來
git clone https://github.com/zouxiaomingya/blog
cd blog
npm i
npm start
複製代碼
全文章,若有錯誤或不嚴謹的地方,請務必給予指正,謝謝!
參考: