[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();
[Redux] Extracting Container Components (FilterLink)的更多相关文章
- [Redux] Extracting Container Components -- Complete
Clean TodoApp Component, it doesn't need to receive any props from the top level component: const To ...
- [Redux] Extracting Container Components -- VisibleTodoList
Code to be refacted: const TodoList = ({ todos, onTodoClick }) => ( <ul> {todos.map(todo =& ...
- [Redux] Redux: Extracting Container Components -- AddTodo
Code to be refactored: const AddTodo = ({ onAddClick }) => { let input; return ( <div> < ...
- [Redux] Extracting Presentational Components -- Footer, FilterLink
Code to be refactored: let nextTodoId = 0; class TodoApp extends Component { render() { const { todo ...
- [Redux] Extracting Presentational Components -- AddTodo
The code to be refactored: let nextTodoId = 0; class TodoApp extends Component { render() { const { ...
- [Redux] Extracting Presentational Components -- TodoApp
Finally, I just noticed that the to-do app component doesn't actually have to be a class. I can turn ...
- [Redux] Extracting Presentational Components -- Todo, TodoList
Code to be refactored: let nextTodoId = 0; class TodoApp extends Component { render() { const { todo ...
- Presentational and Container Components
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0 There’s a simple pattern I fi ...
- (翻译)React Container Components
原文:Container Components Container Components 在 React 模式上对我的代码有最深远影响的一个模式叫 container component 模式. 在 ...
随机推荐
- 近段时间学习html和CSS的一些细碎总结
1.边框圆角属性:border-radius,取值能够是 百分比 / 自己定义长度,不能够取负值.假设是圆,将高度和宽度设置相等,而且将border-radius设置为100% 2.IE6,IE7,I ...
- Zend Framework 留言本实战(转)
一.环境搭建和ZF安装 *[注]本节内容大部分来至Zend Framework官方手册 1.1 Zend Framework下载 Zend Framework 使 ...
- 使用CXF+spring创建一个web的接口项目
一.web project整合spring 1.1.打开Myeclipse,建立web project(eclipse为dynamic web project),使用J2EE5.0. 1.2.加入Sr ...
- NHIBERNATE之映射文件配置说明(转载4)
二十.自定义值类型 开发者创建属于他们自己的值类型也是很容易的.比如说,你可能希望持久化Int64类型的属性, 持久化成为VARCHAR 字段.NHibernate没有内置这样一种类型.自定义类型 ...
- 添加序号列(SQL Server)
SELECT ROW_NUMBER() OVER (ORDER BY 实际缴费金额 ) AS A, --序号 RANK() OVER (ORDER BY 实际缴费金额 ) AS B, --相同跳过从新 ...
- 忘记了SqlServer的SA密码怎么办
转自 http://v-consult.be/2011/05/26/recover-sa-password-microsoft-sql-server-2008-r2/ 如果忘记了sa密码,并且wind ...
- 使用jQuery操作元素的属性与样式
本文学习如何使用jQuery获取和操作元素的属性和CSS样式. 元素属性和Dom属性 对于下面这样一个标签元素: <img id='img' src="1.jpg" alt= ...
- C#高级知识点概要(3) - 特性、自动属性、对象集合初始化器、扩展方法、Lambda表达式和Linq查询
1.特性(Attributes) 特性(Attributes),MSDN的定义是:公共语言运行时允许你添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注,如类型.字段.方法 ...
- 武汉科技大学ACM :1001: A + B Problem
Problem Description Calculate A + B. Input Each line will contain two integers A and B. Process to e ...
- IOS添加自定义字体库
1.将需要的字体库xxx.ttf添加到工程中,注意一定要在copy bundle resources中存在,如果没有添加上去 2.在info.plist 文件中添加 fonts provided by ...