码迷,mamicode.com
首页 > 其他好文 > 详细

[Redux] Extracting Container Components (FilterLink)

时间:2016-01-29 20:00:43      阅读:208      评论:0      收藏:0      [点我收藏+]

标签:

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)

标签:

原文地址:http://www.cnblogs.com/Answer1215/p/5169500.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!