Learn how to avoid the boilerplate of passing the props down the intermediate components by introducing more container components.

Code to be refactored:

  1. const FilterLink = ({
  2. filter,
  3. currentFilter,
  4. children,
  5. onClick
  6. }) => {
  7. if (filter === currentFilter) {
  8. return <span>{children}</span>;
  9. }
  10.  
  11. return (
  12. <a href='#'
  13. onClick={e => {
  14. e.preventDefault();
  15. onClick(filter);
  16. }}
  17. >
  18. {children}
  19. </a>
  20. );
  21. };
  22.  
  23. const Footer = ({
  24. visibilityFilter,
  25. onFilterClick
  26. }) => (
  27. <p>
  28. Show:
  29. {' '}
  30. <FilterLink
  31. filter='SHOW_ALL'
  32. currentFilter={visibilityFilter}
  33. onClick={onFilterClick}
  34. >
  35. All
  36. </FilterLink>
  37. {', '}
  38. <FilterLink
  39. filter='SHOW_ACTIVE'
  40. currentFilter={visibilityFilter}
  41. onClick={onFilterClick}
  42. >
  43. Active
  44. </FilterLink>
  45. {', '}
  46. <FilterLink
  47. filter='SHOW_COMPLETED'
  48. currentFilter={visibilityFilter}
  49. onClick={onFilterClick}
  50. >
  51. Completed
  52. </FilterLink>
  53. </p>
  54. );
  55.  
  56. const TodoApp = ({
  57. todos,
  58. visibilityFilter
  59. }) => (
  60. <div>
  61. ...
  62. ...
  63. ...
  64. <Footer
  65. visibilityFilter={visibilityFilter}
  66. onFilterClick={filter =>
  67. store.dispatch({
  68. type: 'SET_VISIBILITY_FILTER',
  69. filter
  70. })
  71. }
  72. />
  73. </div>
  74. );

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.

  1. TodoApp (C) --> Footer (P) --> FilterLink (P)

If we met this kind of problem, what we can do is, break FilterLink into:

  1. 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:

  1. TodoApp (C) --> Footer (P) --> FilterLink (C) --> Link (P)

Link (P):

  1. const Link = ({
  2. active,
  3. children,
  4. onClick
  5. }) => {
  6. if (active) {
  7. return <span>{children}</span>;
  8. }
  9.  
  10. return (
  11. <a href='#'
  12. onClick={e => {
  13. e.preventDefault();
  14. onClick();
  15. }}
  16. >
  17. {children}
  18. </a>
  19. );
  20. };

FilterLink (C):

  1. class FilterLink extends Component {
  2. componentDidMount() {
  3. this.unsubscribe = store.subscribe(() =>
  4. this.forceUpdate()
  5. );
  6. }
  7.  
  8. componentWillUnmount() {
  9. this.unsubscribe();
  10. }
  11.  
  12. render() {
  13. const props = this.props;
  14. const state = store.getState();
  15.  
  16. return (
  17. <Link
  18. active={props.filter === state.visibilityFilter}
  19. onClick={()=>{
  20. store.dispatch({
  21. type: 'SET_VISIBILITY_FILTER',
  22. filter: props.filter
  23. })
  24. }}
  25. >
  26. {props.children}
  27. </Link>
  28. );
  29. }
  30. }

Footer (P):

  1. const Footer = () => (
  2. <p>
  3. Show:
  4. {' '}
  5. <FilterLink
  6. filter='SHOW_ALL'
  7. >
  8. All
  9. </FilterLink>
  10. {', '}
  11. <FilterLink
  12. filter='SHOW_ACTIVE'
  13. >
  14. Active
  15. </FilterLink>
  16. {', '}
  17. <FilterLink
  18. filter='SHOW_COMPLETED'
  19. >
  20. Completed
  21. </FilterLink>
  22. </p>
  23. );

All:

  1. const todo = (state, action) => {
  2. switch (action.type) {
  3. case 'ADD_TODO':
  4. return {
  5. id: action.id,
  6. text: action.text,
  7. completed: false
  8. };
  9. case 'TOGGLE_TODO':
  10. if (state.id !== action.id) {
  11. return state;
  12. }
  13.  
  14. return {
  15. ...state,
  16. completed: !state.completed
  17. };
  18. default:
  19. return state;
  20. }
  21. };
  22.  
  23. const todos = (state = [], action) => {
  24. switch (action.type) {
  25. case 'ADD_TODO':
  26. return [
  27. ...state,
  28. todo(undefined, action)
  29. ];
  30. case 'TOGGLE_TODO':
  31. return state.map(t =>
  32. todo(t, action)
  33. );
  34. default:
  35. return state;
  36. }
  37. };
  38.  
  39. const visibilityFilter = (
  40. state = 'SHOW_ALL',
  41. action
  42. ) => {
  43. switch (action.type) {
  44. case 'SET_VISIBILITY_FILTER':
  45. return action.filter;
  46. default:
  47. return state;
  48. }
  49. };
  50.  
  51. const { combineReducers } = Redux;
  52. const todoApp = combineReducers({
  53. todos,
  54. visibilityFilter
  55. });
  56.  
  57. const { createStore } = Redux;
  58. const store = createStore(todoApp);
  59.  
  60. const { Component } = React;
  61.  
  62. const Link = ({
  63. active,
  64. children,
  65. onClick
  66. }) => {
  67. if (active) {
  68. return <span>{children}</span>;
  69. }
  70.  
  71. return (
  72. <a href='#'
  73. onClick={e => {
  74. e.preventDefault();
  75. onClick();
  76. }}
  77. >
  78. {children}
  79. </a>
  80. );
  81. };
  82.  
  83. class FilterLink extends Component {
  84. componentDidMount() {
  85. this.unsubscribe = store.subscribe(() =>
  86. this.forceUpdate()
  87. );
  88. }
  89.  
  90. componentWillUnmount() {
  91. this.unsubscribe();
  92. }
  93.  
  94. render() {
  95. const props = this.props;
  96. const state = store.getState();
  97.  
  98. return (
  99. <Link
  100. active={props.filter === state.visibilityFilter}
  101. onClick={()=>{
  102. store.dispatch({
  103. type: 'SET_VISIBILITY_FILTER',
  104. filter: props.filter
  105. })
  106. }}
  107. >
  108. {props.children}
  109. </Link>
  110. );
  111. }
  112. }
  113.  
  114. const Footer = () => (
  115. <p>
  116. Show:
  117. {' '}
  118. <FilterLink
  119. filter='SHOW_ALL'
  120. >
  121. All
  122. </FilterLink>
  123. {', '}
  124. <FilterLink
  125. filter='SHOW_ACTIVE'
  126. >
  127. Active
  128. </FilterLink>
  129. {', '}
  130. <FilterLink
  131. filter='SHOW_COMPLETED'
  132. >
  133. Completed
  134. </FilterLink>
  135. </p>
  136. );
  137.  
  138. const Todo = ({
  139. onClick,
  140. completed,
  141. text
  142. }) => (
  143. <li
  144. onClick={onClick}
  145. style={{
  146. textDecoration:
  147. completed ?
  148. 'line-through' :
  149. 'none'
  150. }}
  151. >
  152. {text}
  153. </li>
  154. );
  155.  
  156. const TodoList = ({
  157. todos,
  158. onTodoClick
  159. }) => (
  160. <ul>
  161. {todos.map(todo =>
  162. <Todo
  163. key={todo.id}
  164. {...todo}
  165. onClick={() => onTodoClick(todo.id)}
  166. />
  167. )}
  168. </ul>
  169. );
  170.  
  171. const AddTodo = ({
  172. onAddClick
  173. }) => {
  174. let input;
  175.  
  176. return (
  177. <div>
  178. <input ref={node => {
  179. input = node;
  180. }} />
  181. <button onClick={() => {
  182. onAddClick(input.value);
  183. input.value = '';
  184. }}>
  185. Add Todo
  186. </button>
  187. </div>
  188. );
  189. };
  190.  
  191. const getVisibleTodos = (
  192. todos,
  193. filter
  194. ) => {
  195. switch (filter) {
  196. case 'SHOW_ALL':
  197. return todos;
  198. case 'SHOW_COMPLETED':
  199. return todos.filter(
  200. t => t.completed
  201. );
  202. case 'SHOW_ACTIVE':
  203. return todos.filter(
  204. t => !t.completed
  205. );
  206. }
  207. }
  208.  
  209. let nextTodoId = 0;
  210. const TodoApp = ({
  211. todos,
  212. visibilityFilter
  213. }) => (
  214. <div>
  215. <AddTodo
  216. onAddClick={text =>
  217. store.dispatch({
  218. type: 'ADD_TODO',
  219. id: nextTodoId++,
  220. text
  221. })
  222. }
  223. />
  224. <TodoList
  225. todos={
  226. getVisibleTodos(
  227. todos,
  228. visibilityFilter
  229. )
  230. }
  231. onTodoClick={id =>
  232. store.dispatch({
  233. type: 'TOGGLE_TODO',
  234. id
  235. })
  236. }
  237. />
  238. <Footer />
  239. </div>
  240. );
  241.  
  242. const render = () => {
  243. ReactDOM.render(
  244. <TodoApp
  245. {...store.getState()}
  246. />,
  247. document.getElementById('root')
  248. );
  249. };
  250.  
  251. store.subscribe(render);
  252. render();

[Redux] Extracting Container Components (FilterLink)的更多相关文章

  1. [Redux] Extracting Container Components -- Complete

    Clean TodoApp Component, it doesn't need to receive any props from the top level component: const To ...

  2. [Redux] Extracting Container Components -- VisibleTodoList

    Code to be refacted: const TodoList = ({ todos, onTodoClick }) => ( <ul> {todos.map(todo =& ...

  3. [Redux] Redux: Extracting Container Components -- AddTodo

    Code to be refactored: const AddTodo = ({ onAddClick }) => { let input; return ( <div> < ...

  4. [Redux] Extracting Presentational Components -- Footer, FilterLink

    Code to be refactored: let nextTodoId = 0; class TodoApp extends Component { render() { const { todo ...

  5. [Redux] Extracting Presentational Components -- AddTodo

    The code to be refactored: let nextTodoId = 0; class TodoApp extends Component { render() { const { ...

  6. [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 ...

  7. [Redux] Extracting Presentational Components -- Todo, TodoList

    Code to be refactored: let nextTodoId = 0; class TodoApp extends Component { render() { const { todo ...

  8. Presentational and Container Components

    https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0 There’s a simple pattern I fi ...

  9. (翻译)React Container Components

    原文:Container Components Container Components 在 React 模式上对我的代码有最深远影响的一个模式叫 container component 模式. 在 ...

随机推荐

  1. 近段时间学习html和CSS的一些细碎总结

    1.边框圆角属性:border-radius,取值能够是 百分比 / 自己定义长度,不能够取负值.假设是圆,将高度和宽度设置相等,而且将border-radius设置为100% 2.IE6,IE7,I ...

  2. Zend Framework 留言本实战(转)

    一.环境搭建和ZF安装              *[注]本节内容大部分来至Zend Framework官方手册       1.1 Zend Framework下载 Zend Framework 使 ...

  3. 使用CXF+spring创建一个web的接口项目

    一.web project整合spring 1.1.打开Myeclipse,建立web project(eclipse为dynamic web project),使用J2EE5.0. 1.2.加入Sr ...

  4. NHIBERNATE之映射文件配置说明(转载4)

    二十.自定义值类型   开发者创建属于他们自己的值类型也是很容易的.比如说,你可能希望持久化Int64类型的属性, 持久化成为VARCHAR 字段.NHibernate没有内置这样一种类型.自定义类型 ...

  5. 添加序号列(SQL Server)

    SELECT ROW_NUMBER() OVER (ORDER BY 实际缴费金额 ) AS A, --序号 RANK() OVER (ORDER BY 实际缴费金额 ) AS B, --相同跳过从新 ...

  6. 忘记了SqlServer的SA密码怎么办

    转自 http://v-consult.be/2011/05/26/recover-sa-password-microsoft-sql-server-2008-r2/ 如果忘记了sa密码,并且wind ...

  7. 使用jQuery操作元素的属性与样式

    本文学习如何使用jQuery获取和操作元素的属性和CSS样式. 元素属性和Dom属性 对于下面这样一个标签元素: <img id='img' src="1.jpg" alt= ...

  8. C#高级知识点概要(3) - 特性、自动属性、对象集合初始化器、扩展方法、Lambda表达式和Linq查询

    1.特性(Attributes) 特性(Attributes),MSDN的定义是:公共语言运行时允许你添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注,如类型.字段.方法 ...

  9. 武汉科技大学ACM :1001: A + B Problem

    Problem Description Calculate A + B. Input Each line will contain two integers A and B. Process to e ...

  10. IOS添加自定义字体库

    1.将需要的字体库xxx.ttf添加到工程中,注意一定要在copy bundle resources中存在,如果没有添加上去 2.在info.plist 文件中添加 fonts provided by ...