[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();
时间: 2025-01-09 15:00:52

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

[Redux] Extracting Presentational Components -- Footer, FilterLink

Code to be refactored: let nextTodoId = 0; class TodoApp extends Component { render() { const { todos, visibilityFilter } = this.props; const visibleTodos = getVisibleTodos( todos, visibilityFilter ); return ( <div> <input ref={node => { this.

[Redux] Extracting Presentational Components -- AddTodo

The code to be refactored: let nextTodoId = 0; class TodoApp extends Component { render() { const { todos, visibilityFilter } = this.props; const visibleTodos = getVisibleTodos( todos, visibilityFilter ); return ( <div> <input ref={node => { t

[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 it into a function. I prefer to-do that when possible. Code to be refactored: class TodoApp extends Component { render() { const { todos, visibilityF

Learning OpenCV Lecture 6 (Extracting Lines,Contours, and Components)

In this chapter, we will cover: Detecting image contours with the Canny operator Detecting lines in images with the Hough transform Fitting a line to a set of points Extracting the components' contours Computing components' shape descriptors Detectin

React &amp; Redux 的一些基本知识点

一.React.createClass 跟 React.Component 的区别在于后者使用了ES6的语法,用constructor构造器来构造默认的属性和状态. 1. React.createClass import React from 'react';       const Contacts = React.createClass({             render() {                return (                    <div><

关于React的Container&amp;Presentational Component模型结构分析

react.js javascript 3 之前翻译了两篇关于Container&Presentational Component模型的文章,一篇是基础的Container和Component的定义,另外一篇是进阶版,因为翻译的太烂,感觉有很多错误,所以只放原文链接. 在这里我想讨论一下我自己对这个模型的一些想法. 注:便于书写,下面统一把Container&Presentational Components模型翻译为容器&展示组件模型 注:下面图片中的components文件夹指

react系列(五)在React中使用Redux

上一篇展示了Redux的基本使用,可以看到Redux非常简单易用,不限于React,也可以在Angular.Vue等框架中使用,只要需要Redux的设计思想的地方,就可以使用它. 这篇主要讲解在React中使用Redux,首先是安装. 安装React Redux yarn add redux yarn add react-redux 有两个概念: 1.容器组件(Container Components) 2.展示组件(Presentational Components) 展示组件 更关注数据展示

【译】Redux 还是 Mobx,让我来解决你的困惑!

原文地址:Redux or MobX: An attempt to dissolve the Confusion 原文作者:rwieruch 我在去年大量的使用了 Redux,但我最近都在使用 Mobx 来做状态(state)管理.似乎现在社区里关于该选什么来替代 Redux 很自然地成为了一件困惑的事.开发者不确定该选择哪种解决方案.这个问题并不只是出现在 Redux 与 Mobx 上.无论何时,只要存在选择,人们就会好奇最好的解决问题的方式是什么.我现在写的这些是为了解决 Redux 和 M

[Angular 2] Passing Observables into Components with Async Pipe

The components inside of your container components can easily accept Observables. You simply define your custom @Input then use the Async pipe when you pass the Observable in. This lesson walks you through the process of passing an Observable into a