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>)
}
FilterLink
还记得之前那段冗长的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>
)
}
Footer
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.