[Redux] Extracting Container Components (FilterLink)
Learn how to avoid the boilerplate of passing the props down the intermediate components by introducing more container components.
Code to be refactored:
const FilterLink = ({ filter, currentFilter, children, onClick }) => { if (filter === currentFilter) { return <span>{children}</span>; } return ( <a href='#' onClick={e => { e.preventDefault(); onClick(filter); }} > {children} </a> ); }; 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> ); const TodoApp = ({ todos, visibilityFilter }) => ( <div> ... ... ... <Footer visibilityFilter={visibilityFilter} onFilterClick={filter => store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter }) } /> </div> );
Notice in the container component, we pass visibilityFilter to the Footer presentional component, but Footer component actually doesn't do anything about that, just pass down to the FilterLink presentional component. This is the downside for the current approach.
TodoApp (C) --> Footer (P) --> FilterLink (P)
If we met this kind of problem, what we can do is, break FilterLink into:
FilterLink (C) --> Link (P)
We convent FilterLink into a container component and make a new presentional component called 'Link'.
And inside FilterLink, we can use Redux to getState(), everytime the state change, we force the component render itself and remember to unsubscribe when the component will unmount.
Also we move the dispatch action from TodoApp container component to the FilterLink container component. So that in TodoApp, the Footer component looks nice and clean.
So now, it looks like:
TodoApp (C) --> Footer (P) --> FilterLink (C) --> Link (P)
Link (P):
const Link = ({ active, children, onClick }) => { if (active) { return <span>{children}</span>; } return ( <a href='#' onClick={e => { e.preventDefault(); onClick(); }} > {children} </a> ); };
FilterLink (C):
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={()=>{ store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter: props.filter }) }} > {props.children} </Link> ); } }
Footer (P):
const Footer = () => ( <p> Show: {' '} <FilterLink filter='SHOW_ALL' > All </FilterLink> {', '} <FilterLink filter='SHOW_ACTIVE' > Active </FilterLink> {', '} <FilterLink filter='SHOW_COMPLETED' > Completed </FilterLink> </p> );
All:
const todo = (state, action) => { switch (action.type) { case 'ADD_TODO': return { id: action.id, text: action.text, completed: false }; case 'TOGGLE_TODO': if (state.id !== action.id) { return state; } return { ...state, completed: !state.completed }; default: return state; } }; const todos = (state = [], action) => { switch (action.type) { case 'ADD_TODO': return [ ...state, todo(undefined, action) ]; case 'TOGGLE_TODO': return state.map(t => todo(t, action) ); default: return state; } }; const visibilityFilter = ( state = 'SHOW_ALL', action ) => { switch (action.type) { case 'SET_VISIBILITY_FILTER': return action.filter; default: return state; } }; const { combineReducers } = Redux; const todoApp = combineReducers({ todos, visibilityFilter }); const { createStore } = Redux; const store = createStore(todoApp); const { Component } = React; const Link = ({ active, children, onClick }) => { if (active) { return <span>{children}</span>; } return ( <a href='#' onClick={e => { e.preventDefault(); onClick(); }} > {children} </a> ); }; 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={()=>{ store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter: props.filter }) }} > {props.children} </Link> ); } } const Footer = () => ( <p> Show: {' '} <FilterLink filter='SHOW_ALL' > All </FilterLink> {', '} <FilterLink filter='SHOW_ACTIVE' > Active </FilterLink> {', '} <FilterLink filter='SHOW_COMPLETED' > Completed </FilterLink> </p> ); const Todo = ({ onClick, completed, text }) => ( <li onClick={onClick} style={{ textDecoration: completed ? 'line-through' : 'none' }} > {text} </li> ); const TodoList = ({ todos, onTodoClick }) => ( <ul> {todos.map(todo => <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} /> )} </ul> ); const AddTodo = ({ onAddClick }) => { let input; return ( <div> <input ref={node => { input = node; }} /> <button onClick={() => { onAddClick(input.value); input.value = ''; }}> Add Todo </button> </div> ); }; const getVisibleTodos = ( todos, filter ) => { switch (filter) { case 'SHOW_ALL': return todos; case 'SHOW_COMPLETED': return todos.filter( t => t.completed ); case 'SHOW_ACTIVE': return todos.filter( t => !t.completed ); } } let nextTodoId = 0; 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 /> </div> ); const render = () => { ReactDOM.render( <TodoApp {...store.getState()} />, document.getElementById('root') ); }; store.subscribe(render); render();
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具