这篇文章所述的思想最终进化成了一个简单的状态管理模式,称React StateUp Pattern,详细介绍请参阅:https://segmentfault.com/a/11...
写了一个非常简单的实验性Pattern,暂且称为PurifiedComponent。目的是为了解决React Component在重用的时候,state持久和方法重用的问题。
React组件和state的生命周期不一致,即使是在一个对话框内,也可能因为折叠或者平移,一些组件消失但是他们的state还需要持久;这种情况下在this.state
内存放状态并不能解决问题,因为如果这个组件在渲染时消失了,它的state也没了;
React官方的说法是应该把state写到相关组件的共同祖先上去,这一点在实现逻辑上本身没有问题,有问题的地方是重用很不方便;很多view state,例如和输入框配合的校验函数或者出错信息,他们本身就应该是和组件一起使用的,离开组件去单独维护没有意义;
解决这个问题的思路是,把组件需要的view state独立构建对象;持久化state的责任,托管到父组件去,但是对组件操作的责任,仍然留在子组件内,换句话说,子组件和它的state是由父组件bind在一起的。
class PurifiedComponent extends React.PureComponent { setState(props) { let { state,name,setState } = this.props setState({ [name] : Object.assign(new state.constructor(),state,props) }) } } class Impure extends React.Component { constructor(props) { super() this.state = { state: new props.component.State() } } render() { let Component = this.props.component return <Component state={this.state.state} setState={this.setState.bind(this)} name="state" /> } }
上面的两个class是基础类;PurifiedComponent在React.PureComponent基础上实现了一个setState
方法。
使用方式看下面的例子,Child1
是一个独立PurifiedComponent的例子,Composite
是组合的例子;
继承自PurifiedComponent
的类需要提供一个静态类变量State
,它是一个class;这个类是用于描述状态的类,即所谓的view state;它也应该具有行为,尤其是那些计算computed的方法,对父组件来说可以直接访问;
PurifiedComponent不在类结构内维护state,即不使用this.state
;创建和保存它的state是它的父容器的职责;父容器可以通过new Child1.State()
创建这个state;这是第一个约定;
第二个约定是,这些类需要有三个props:
state,从外部传入的state;
name,state在父容器组件的state内的property name;
setState,父容器的setState方法;
有了这三者后,子组件就可以做stateful的工作,用传入的state和setState工作;name原则来说不是逻辑需要的,但可以结合PureComponent避免不必要的刷新。
class Child1 extends PurifiedComponent { static State = class State { constructor() { this.label = '' } } render() { let {state,name} = this.props console.log(`render ${name}`) return ( <div> <button style={{width: 64,height: 24}} onClick={() => this.setState({ label: state.label + 'a' })} >{state.label}</button> </div> ) } }
Composite是一个组合,目前没有设计使用数组组合的方式,只看看用Property Name来组合的办法,这个Composite和它的子组件一样没有使用自己的this.state
,这显示了这种Pattern是可以自下而上组成树的;
Composite的构造函数里有一个this.ssb
,它表示的是bound函数,之所以在对象上创建是为了保持它的引用稳定,这样在向child传递三个参数时,setState和name都是恒定的,只有第一个state变化时子组件会重新渲染;这是我们需要的特性;
class Composite extends PurifiedComponent { static State = class State { constructor() { this.child1 = new Child1.State() this.child2 = new Child1.State() } } constructor() { super() this.ssb = this.setState.bind(this) } render() { return ( <div> <Child1 state={this.props.state.child1} setState={this.ssb} name="child1" /> <Child1 state={this.props.state.child2} setState={this.ssb} name="child2" /> </div> ) } } class App extends Component { render() { return <Impure component={Composite} /> } } export default App
最后的Impure
是一键拦截这种层层向上提升state holder的行为,它可以作为一个stateful的组件,用它的this.state
来装载所有内含的PurifiedComponent构成的树。换句话说你不用担心把组件写成Purified模式不好重用,如果你需要传统的方式使用,Impure一下即可。
上述代码虽然简单但是可以工作,我会在生产环境中尝试一下。