Container

所谓container,它定义behavior并向子组件传递behavior以及其他所需参数。我们的TodoApp就是一个container,它是所有子组件的data和behavior来源,但是这样会使得封装性变差,各子组件的功能性也降低了。试想如果所有的子组件都是Presentational Component的话,那么所有子组件拼接起来其实就是一个view模板。 这不是我们所追求的组件化,所以我们将分解TodoApp集成的behavior以及data,分配给下一级的Container Components,让其自主管理行为和data。

const Footer = ({ visibilityFilter, onFilterClick }) => (
  <p>
    Show:
    {' '}
    <FilterLink
      filter='SHOW_ALL'
      currentFilter={visibilityFilter}
      onClick={onFilterClick}
    >
      ALL
    </FilterLink>
  </p>
)

现在Footer接受两个参数,这两个参数是通过TodoApp传递下来的,在此Footer只是起到一个‘媒介’作用,并且Footer本身不具有直接的功能,所以我们将Footer改造成一个Purely Presentational Component:

const Footer = () => (
  <p>
    Show:
    {' '}
    <FilterLink
      filter='SHOW_ALL'
    >
      Al
    </FilterLink>
    {', '}
    <FilterLink
      filter='SHOW_ACTIVE'
    >
      Active
    </FilterLink>
    {', '}
    <FilterLink
      filter='SHOW_COMPLETED'
    >
      Completed
    </FilterLink>
  </p>
)

我们可以注意到,由于Footer不再传递参数,FilterLink也不再接收参数,我们知道FilterLink才是真正的action触发者,所以我们可以让它作为Container,于是它便可以自主指派behavior ,根据给定的filter参数,让子组件决定不同的behavior:

class FilterLink extends Component {
  render() {
    const props = this.props;
    const state = store.getState();

    return (
      <Link
        active={
          props.filter ===
          state.visibilityFilter
        }
        onClick={() =>   //behavior
          store.dispatch({
            type: 'SET_VISIBILITY_FILTER',
            filter: props.filter
          })
        }
      >
        {props.children} // refering to All/Active/ompleted
      </Link>
    );
  }
}

下一步就是为这个Container指定Purely Presentational Component => Link,它只关注如何渲染:

const Link = ({active, children, onClick}) => {
  return active ? <span>{children}</span> :
    (
      <a href='#'
        onClick={e => {
          e.preventDefault();
           onClick();
         }}
       >
      {children}
      </a>
    )
}

现在把目光转回FilterLink,其中的state是通过store.getState()传回的,但如果state改变时,并不会触发FilterLink的Re-render,因为我们对于store的订阅使用的注册者是render函数,render函数内部渲染的是TodoApp。 也就说,每次state改变,我们都会Re-render整个TodoApp,这在于效率上是非常糟糕的设计,所以我们需要让它具有自我更新的能力,在之后我们会将不再会subscribe此前定义的render函数。我们将利用组件的生命周期里的两个状态:

  1. ComponentDidMount
  2. ComponentWillUnMount

还有一个方法叫:forceUpdate.

现在是不是能猜到我们要干什么了? 我们将在组件挂载之后向store订阅注册一个函数,这个函数将会调用forceUpdate令其在state变化时改变自己的状态, 在componentWillUnMount时进行取消订阅。

class FilterLink extends Component {
  componentDidMount () {
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    )
  }
  componentWillUnMount () {
    this.unsubscribe()
  }
  render() {
    const props = this.props;
    const state = store.getState();

    return (
      <Link
        active={
          props.filter ===
          state.visibilityFilter
        }
        onClick={() =>   //behavior
          store.dispatch({
            type: 'SET_VISIBILITY_FILTER',
            filter: props.filter
          })
        }
      >
        {props.children} // refering to All/Active/ompleted
      </Link>
    );
  }
}

AddTodo

这个组件内部并没有读取state,它只是单纯的决定一个行为: 将input.value传入dispatch. 并且也同时决定了如何渲染。 所以这个组件很特殊,它既不是container 也不是 presentational component。

const AddTodo = () => {
  let input,
      nextTodoId = 0

  return (
    <div>
      <input ref={node => input=node} />
      <button
        onClick={() => {
          store.dispatch({
            type: 'ADD_TODO',
            id: nextTodoId++,
            text: input.value 
          })
          input.value = '';
      }}>
        ADD
      </button>
    </div>
  )
}

VisibleTodoList

回顾之前定义的组件TodoList,它从TodoApp接收参数,现在我们也将它改造成container:

class VisibleTodoList extends Component {
  visibilityFilter (state = 'SHOW_ALL', action) {  // we included this method to find out what data to show
    switch (action.type) {
      case 'SET_VISIBILITY_FILTER':
        return action.filter
      default:
        return state
    }
  }
  componentDidMount () {
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    )
  }
  componentWillUnMount () {
    this.unsubscribe()
  }
  render() {
    const props = this.props;
    const state = store.getState();

    return (
      <TodoList
        todos={
          this.getVisibleTodos(
            state.todos,
            state.visibilityFilter
          )
        }
        onTodoClick={id =>  //behavior
          store.dispatch({
            type: 'TOGGLE_TODO',
            id
          })            
        }
      />
    );
  }
}

现在我们的TodoApp变成了这样:

const TodoApp = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
)

清晰明了,省去了自顶部传输所有的behavior和data,每个组件拥有自己的功能,独立更新自己。 等等,我们别忘了删掉之前的render函数,也不再订阅它至store, 取代它的是直接使用ReactDOM.render

ReactDOM.render(
  <TodoApp/>,
  document.getElementById('root')
)

看看效果:JSBin

results matching ""

    No results matching ""