1

这篇文章所述的思想最终进化成了一个简单的状态管理模式,称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:

  1. state,从外部传入的state;

  2. name,state在父容器组件的state内的property name;

  3. 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一下即可。

上述代码虽然简单但是可以工作,我会在生产环境中尝试一下。


uglee
1.1k 声望1.2k 粉丝