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