一个项目,一个复杂的逻辑,我以为状态管理显得尤其的重要,状态管理的好很差,直接体现了一个项目的逻辑性、可读性、维护性等是否清晰,易读,和高效。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
复制代码
全文章,若有错误或不严谨的地方,请务必给予指正,谢谢!
参考: