Purely Presentational Component

对于这个名词我们在counter example里解释过:

Purely Presentational Component,这种组件负责渲染出view,而不在意行为和数据。

那么根据我们的TodoApp,我们能抽象出一些什么组件呢?

见如下注释:

/**
 * Compenents
 *
 *  Todo
 *    @param {Function} onClick
 *                      Dispatch 'TOGGLE_TODO' action
 *    @param {Boolean}  completed
 *                      When todo was clicked, toggle action will invert its state
 *    @param {String}   text
 *                      Content of todoItem, from AddTodo's loacal variable 'input'
 *                      input refers to input element by using ref={node => input = node}
 * 
 *  TodoList
 *    @param {Array}    todos
 *                      Todos array filtered by visibilityFilter
 *    @param {Function} onTodoClick
 *                      Passed to Todo as @param {Function} onClick
 *                      
 *  AddTodo
 *    @param {Function} onAddClick
 *                      Dispatch 'ADD_TODO' action
 * 
 *  FilterLink
 *    @param {String}   filter
 *                      Specify the state of visibilityFilter
 *    @param {String}   currentFilter
 *                      Passed from Footer @param {String} visibilityFilter
 *    @param {Function} onClick
 *                      @param {String} filter => @param {String} filter
 *                      Dispatch 'SET_VISIBILITY_FILTER' action
 * 
 *  Footer
 *    @param {String}   visibilityFilter
 *                      Passed from @param {String} visibilityFilter
 *                       
 *    @param {Function} onFilterClick
 *                      Passed to FilterLink @param {Function} onClick
 *                      Dispatch 'SET_VISIBILITY_FILTER' action
 *  TodoApp
 *    @param {Array}    todos
 *                      store.getState().todos
 *    @param {String}   visibilityFilter
 *                      store.getState().visibilityFilter
 */

Oh my god... 信息量有点多不是吗,不过没关系,我们将一个个实现它们,在这个过程中我们会梳理清楚它们之间的关系。

Todo

这个组件将会作为Todos的子组件,它负责返回一个todoItem. 记住,以下如不特别说明,所有组件都是Purely Presentational Component. 它将接受的参数有: onClick , completed , text

const Todo = ({onClick, completed, text}) => (
  <li
    style={{
      textDecoration: completed ? 'line-through' : 'none',
      cursor: 'pointer'
    }}
    onClick={onClick} //点击后将会dispatch一个action, type: 'TOGGLE_TODO'
  >
    {text}
  </li>
)

TodoList

这是个Secondary Level 组件,它的层级仅次于TodoApp. 它接受的参数是todos , onTodoClick . todos即state里的todos数组,onTodoClick就是Todo组件里的onClick的响应函数,这个属性只是借由TodoList为媒介进行属性传递,我们将在后面消除这种无谓的”媒介“传参,注意一点,渲染数组数据返回的组件时,需要给每一个子组件加上key属性,至于原因,参见官方文档

Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo => 
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

AddTodo

同样是一个Secondary Level 组件,它只有一个参数: onAddClick ,它不再包含任何子组件。

const AddTodo = ({onAddClick}) => {
  let input
  return(
    <div>
      <input ref={node => {
        input = node
      }} />
      <button onClick={() => {
        onAddClick(input.value)
        input.value = ''
      }}>
        Add Todo
      </button>
    </div>)
}

还记得之前那段冗长的a元素吗,我们反复给其添加了几乎相同的属性,我们完全可以将其封装成一个组件,它接受的参数有:

filter , onClick , children ,children属性,见官方文档.

const FilterLink = ({filter, onClick, children}) => (
  <a href='#'
    onClick={e => {
      e.preventDefault()
      onClick(filter)
    }}
  >
    {children}
  </a>
)

稍微优化一下,我们希望当点击对应链接时,返回一个非a元素,那我们需要引入第四个参数 :currentFilter ,它其实就是state里的visibilityFilter对应的字符串:

const FilterLink = ({filter, onClick, currentFilter,children}) => {
  return filter === currentFilter ? <span>{children}</span> : (
    <a href='#'
      onClick={e => {
        e.preventDefault()
        onClick(filter)
      }}
    >
      {children}
    </a>
  )
}

Secondary Level Component, 我们用它来包裹FilterLink并且传递参数,看起来貌似多次一举,但是从view层的分块和语义化来看,是值得抽象出这个组件的。 接受的参数有: visibilityFilter , onFilterClick .

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

TodoApp

最后回到我们的Top Level Component -- TodoApp,它不再是presentational component, 因为它的内部对行为做了具体定义,我们为它依次传入Secondary Component所需的参数,它本身接受的参数是 : todos , visibilityFilter

const TodoApp = ({
  todos,
  visibilityFilter
}) => (
  <div>
    <AddTodo
      onAddClick={text => 
        store.dispatch({
          type: 'ADD_TODO',
          id: nextTodoId++,
          text
        })
      }
    />
    <TodoList
      todos={
        getVisibleTodos(
          todos,
          visibilityFilter
        )
      }
      onTodoClick={id =>
        store.dispatch({
          type:'TOGGLE_TODO',
          id
        })
      }
    />
    <Footer
      visibilityFilter={visibilityFilter}
      onFilterClick={filter =>
        store.dispatch({
          type: 'SET_VISIBILITY_FILTER',
          filter
        })
      }
    />
  </div>
)

TodoApp同时也是一个Container Component,下一节,我们将解释它的意思并抽象出几个Container Component.

results matching ""

    No results matching ""