react状态管理的主流方案通常是Redux和Mobx。react
Redux是函数式的解决方案,须要写大量的样板文件,可使用Dva或者Rematch来简化开发。redux
Mobx是经过proxy和defineProperty来劫持数据,对每一个数据变更进行响应。api
在项目里使用Redux和Mobx,都须要配合一些其余的功能库,好比react-redux。数组
若是只是想进行简单的组件通讯,去共享一些数据,Redux和Mobx可能会比较重。若是直接使用context,可能要本身封装一些组件。promise
Unstated是一个轻量级的状态管理工具,而且在api设计的时候沿用react的设计思想,可以快速的理解和上手。若是只是为了解决组件的通讯问题,能够尝试使用它,不须要依赖其余的第三方库。bash
import React, { Component } from 'react';
import { Button, Input } from 'antd';
import { Provider, Subscribe, Container } from 'unstated';
class CounterContainer extends Container {
constructor(initCount) {
super();
this.state = { count: initCount || 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
decrement = () => {
this.setState({ count: this.state.count - 1 });
};
}
const Counter = () => (
<Subscribe to={[CounterContainer]}>
{
counter => (
<div>
<span>{counter.state.count}</span>
<Button onClick={counter.decrement}>-</Button>
<Button onClick={counter.increment}>+</Button>
</div>
)
}
</Subscribe>
);
export default class CounterProvider extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<Provider>
<Counter />
</Provider>
);
}
}
复制代码
Unstated抛出三个对象,分别是Container、Subscribe和Provider。antd
Unstated会使用React.createContext来建立一个StateContext对象,用来进行状态的传递。
app
// 简单处理过的代码
export class Container {
constructor() {
CONTAINER_DEBUG_CALLBACKS.forEach(cb => cb(this));
this.state = null;
this.listeners = [];
}
setState(updater, callback) {
return Promise.resolve().then(() => {
let nextState = null;
if (typeof updater === 'function') {
nextState = updater(this.state);
} else {
nextState = updater;
}
if (nextState === null) {
callback && callback();
}
this.state = Object.assign({}, this.state, nextState);
const promises = this.listeners.map(listener => listener());
return Promise.all(promises).then(() => {
if (callback) {
return callback();
}
});
});
}
subscribe(fn) {
this.listeners.push(fn);
}
unsubscribe(fn) {
this.listeners = this.listeners.filter(f => f !== fn);
}
}
复制代码
Container类包含了setState、subscribe和unsubscribe三个方法和state、listeners两个属性。async
state用来存储数据,listeners用来存放订阅的方法。ide
subscribe和unsubscribe分别用来订阅方法和解除订阅,当setState方法被调用时,会去触发listeners中全部的订阅方法。从代码中能够看出,subscribe订阅的方法要返回一个promise。
setState用来更新state。这个方法和react中的setState相似,能够接收一个对象或者方法,最后会使用Object.assign对state进行合并生成一个新的state。
setState()注意点
不要在设置state后当即读取state
class CounterContainer extends Container {
state = { count: 0 };
increment = () => {
this.setState({ count: 1 });
console.log(this.state.count); // 0
};
}
复制代码
若是须要上一个state来计算下一个state,请传入函数
class CounterContainer extends Container {
state = { count: 0 };
increment = () => {
this.setState(state => {
return { count: state.count + 1 };
});
};
}
复制代码
Unstated的setState返回一个promise,可使用await
class CounterContainer extends Container {
state = { count: 0 };
increment = async () => {
await this.setState({ count: 1 });
console.log(this.state.count); // 1
};
}
复制代码
// 简单处理过的代码
class Subscribe extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.instances = [];
this.unmounted = false;
}
componentWillUnmount() {
this.unmounted = true;
this.unsubscribe();
}
unsubscribe() {
this.instances.forEach((container) => {
container.unsubscribe(this.onUpdate);
});
}
onUpdate = () => new Promise((resolve) => {
if (!this.unmounted) {
this.setState(DUMMY_STATE, resolve);
} else {
resolve();
}
})
createInstances(map, containers) {
this.unsubscribe();
if (map === null) {
throw new Error('You must wrap your <Subscribe> components with a <Provider>');
}
const safeMap = map;
const instances = containers.map((ContainerItem) => {
let instance;
if (
typeof ContainerItem === 'object' &&
ContainerItem instanceof Container
) {
instance = ContainerItem;
} else {
instance = safeMap.get(ContainerItem);
if (!instance) {
instance = new ContainerItem();
safeMap.set(ContainerItem, instance);
}
}
instance.unsubscribe(this.onUpdate);
instance.subscribe(this.onUpdate);
return instance;
});
this.instances = instances;
return instances;
}
render() {
return (
<StateContext.Consumer>
{
map => this.props.children.apply(
null,
this.createInstances(map, this.props.to),
)
}
</StateContext.Consumer>
);
}
}
复制代码
Unstated的Subscribe是一个react Component,且返回的是StateContext的Consumer。
两个关键的方法是createInstances和onUpdate。
onUpdate
这个方法用来被Container对象进行订阅,调用这个方法会触发Subscribe的setState,进而从新渲染Subscribe组件。
createInstances
createInstances接收两个参数,map是StateContext.Provider传过来的值,第二个参数是组件接收的to这个prop。
对props传进来的Container类进行处理。若是safeMap没有这个Container的实例化对象,那么先实例化一个instance,而后将这个Container增长到safeMap中,Container做为键,instance做为值;若是传进来的safeMap已经有这个Container类的实例,那么直接赋值给instance。Container的实例生成后,订阅onUpdate方法。
// 简单处理过的代码
function Provider(props) {
return (
<StateContext.Consumer>
{
(parentMap) => {
const childMap = new Map(parentMap);
if (props.inject) {
props.inject.forEach((instance) => {
childMap.set(instance.constructor, instance);
});
}
return (
<StateContext.Provider value={childMap}>
{
props.children
}
</StateContext.Provider>
);
}
}
</StateContext.Consumer>
);
}
复制代码
Provider接收inject这个prop,inject是一个数组,数组的每一项都是Container的实例。
经过上面的代码能够看出,context的值是一个map,这个map的键是Container类,值是Container类的实例。Unstated经过Provider的inject属性,让咱们能够作一些Container类初始化的工做。在Subscribe接收的map中,若是已经存在某个Container类的键值对,那么就直接使用这个实例进行处理
import React, { Component } from 'react';
import { Button, Input } from 'antd';
import { Provider, Subscribe, Container } from 'unstated';
class AppContainer extends Container {
constructor(initAmount) {
super();
this.state = {
amount: initAmount || 1,
};
}
setAmount(amount) {
this.setState({ amount });
}
}
class CounterContainer extends Container {
state = {
count: 0,
};
increment(amount) {
this.setState({ count: this.state.count + amount });
}
decrement(amount) {
this.setState({ count: this.state.count - amount });
}
}
function Counter() {
return (
<Subscribe to={[AppContainer, CounterContainer]}>
{
(app, counter) => (
<div>
<span>Count: {counter.state.count}</span>
<Button onClick={() => counter.decrement(app.state.amount)}>-</Button>
<Button onClick={() => counter.increment(app.state.amount)}>+</Button>
</div>
)
}
</Subscribe>
);
}
function App() {
return (
<Subscribe to={[AppContainer]}>
{
app => (
<div>
<Counter />
<span>Amount: </span>
<Input
type="number"
value={app.state.amount}
onChange={(event) => {
app.setAmount(parseInt(event.currentTarget.value, 10));
}}
/>
</div>
)
}
</Subscribe>
);
}
export default class CounterProvider extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const initAppContainer = new AppContainer(3);
return (
<Provider inject={[initAppContainer]}>
<App />
{/* <Counter /> */}
</Provider>
);
}
}复制代码