引言
React作为一个数据驱动的前端框架,状态管理就显得尤为重要。本文旨在对开发实践过程中的实现进行记录及总结。
一、组件内状态管理
- React state
通过 state 管理状态,使用 setState 更新状态,从而更新UI
export default class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0,
otherValue: 0
};
}
handleAdd() {
this.setState((state, props) => ({
...state,
value: ++state.value
}));
}
render() {
return (
<div>
<p>{this.state.value}</p>
<button onClick={() => this.handleAdd}>Add</button>
</div>
);
}
}
复制代码
react自身的状态管理方案缺点:即使只更新state中某个值,也需要带上其他值(otherValue),操作繁琐
- React Hooks
React16.8中正式增加了hooks。通过hooks管理组件内状态,简单易用可扩展。
动机:
在组件之间复用状态逻辑很难
复杂组件变得难以理解
难以理解的 class
React Hooks 的设计目的,就是加强版函数组件,完全不使用"类"来定义组件,就能写出一个全功能的组件。
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>Add</button>
</div>
);
}
export default Counter;
复制代码
对比 Class 组件中,使用react自身的状态管理方法,React Hooks使用useState方法在 Function 组件中创建状态变量、更新状态的方法、赋值初始状态。这样就实现了一个拥有自己的状态的 Function 组件。更加的简介优雅。
在组件中执行副作用操作时(数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用),Function 组件使用Effect Hook 与 Class组件对比将更明显,有兴趣的同学可查看 官网Demo。以及更多的Hook API。
二、跨组件状态管理
当项目业务需求复杂度的不断增加,我们一定会遇到以下问题:
1、如何实现跨组件通信、状态同步以及状态共享?
我们可以使用React提供的context API:
function useCounter() {
let [count, setCount] = useState(0)
let increment = () => setCount(count + 1)
return { count, increment }
}
let Counter = createContext(null)
function CounterDisplay() {
let counter = useContext(Counter)
return (
<div>
<p>You clicked {counter.count} times</p>
<button onClick={counter.increment}>Add</button>
</div>
)
}
function App() {
let counter = useCounter()
return (
<Counter.Provider value={counter}>
<CounterDisplay />
</Counter.Provider>
)
}
复制代码
但是context只是将状态提升至共有的父组件来实现,看似跨组件,实则还是逐级传递来实现组件间通信、状态同步以及状态共享。另外,使用context会使得组件的服用性变差。
2、如何避免组件臃肿?
当组件的业务逻辑非常复杂时,我们会发现代码越写越多,因为我们只能在组件内部去控制数据流,没办法抽离,Model和View都放在了View层,整个组件显得臃肿不堪,难以维护。
3,如何让状态变得可预知,甚至可回溯? 当数据流混乱时,我们一个执行动作可能会触发一系列的setState,我们如何能够让整个数据流变得可“监控”,甚至可以更细致地去控制每一步数据或状态的变更?
4,如何处理异步数据流? react自身并未提供多种处理异步数据流管理的方案,仅用一个setState已经很难满足一些复杂的异步流场景; 这个时候,我们就需要一个专门的状态管理工具!
- React-Redux
Redux 是一款状态管理库,并且提供了 react-redux 库来与 React 亲密配合。
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import App from "app.jsx";
<!--创建 reducer-->
const reducer = (state = { counter: 0 }, action = {}) => {
const { counter } = state;
const { type } = action;
switch (type) {
case 'INCREASE':
return { counter: counter + 1 };
default:
return state;
}
}
<!--创建 store-->
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
复制代码
Provider 组件,其实就是 Context 实现的,提供一个状态供跨组件使用,只需要把 store 给它传过去,所有的子组件就可以通过 props 属性拿到状态值。
app.jsx
import React from "react";
<!--创建 action-->
const increaseAction = { type: "INCREASE" };
const mapStateToProps = state => ({ counter: state.counter });
const mapDispatchToProps = dispatch => ({
onIncreaseHandle: () => dispatch(increaseAction)
});
class App extends React.Component {
render() {
let {
counter,
onIncreaseHandle
} = this.props;
return (
<div>
<h1>{counter}</h1>
<button onClick={onIncreaseHandle}>+1</button>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
复制代码
React-Redux 的核心方法之一 connect,用于从 UI 组件生成容器组件。connect 方法接受两个参数:
mapStateToProps:建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。
mapDispatchToProps:将用户对 UI 组件的操作映射成 Action。
react-redux 7.1的hooks版本提供了useSelector()、useDispatch()、useStore()这3个主要方法,分别对应与mapState、mapDispatch以及直接拿到redux中store的实例。有兴趣的同学可查看
总结
在React中,状态管理分为全局状态管理和局部状态管理,React Hooks简化了局部状态管理,使组件逻辑清晰,渲染方便。但它并不能解决组件间通信问题,而React-Redux正是用来解决该问题。也就是说,React-Redux等状态管理和React Hooks本质上并不矛盾。相反,搭配使用让项目更健壮(如果你需要React-Redux的话)。