[Redux] Generating Containers with connect() from React Redux (AddTodo)

Code to be refacted:

const AddTodo = (props, { store }) => {
  let input;

  return (
    <div>
      <input ref={node => {
        input = node;
      }} />
      <button onClick={() => {
        store.dispatch({
          type: ‘ADD_TODO‘,
          id: nextTodoId++,
          text: input.value
        })
        input.value = ‘‘;
      }}>
        Add Todo
      </button>
    </div>
  );
};
AddTodo.contextTypes = {
  store: React.PropTypes.object
};

Instead of passing:

const AddTodo = (props, { store }) => {

We can only pass ‘props‘, and we get ‘dispatch‘ from the props:

let AddTodo = ({ dispatch }) => {

Then we will create a container component with connect that will inject that the dispatch function as a prop, we will remove the context types because the component generated by connect function will take care of reading this chore from the context.

The first argument to the connect function is map straight to props, but there aren‘t any props for at to-do component that depend on the current state, so I return an empty object. The second argument to connect is map dispatch to props, but at to-do component doesn‘t need any callback props. It just accepts the dispatch function itself, so I‘m returning it as a prop with the same name.

Finally, I‘m calling the function for a second time to specify the component I want to wrap, in this case, AddTodo itself:

AddTodo = connect(
  state => {
    return {};
  },
  dispatch => {
    return {dispatch};
  }
)(AddTodo);

However, it is wasteful to even subscribe to this chore if we don‘t calculate any props from its state. So I‘m replacing the mapStateToProps function with an null, which tells connect that there is no need to subscribe to this store.

Additionally, it‘s pretty common pattern to inject just the dispatch function. This is why if you specify null or any false value in connect as the second argument, you‘re going to get dispatch injected as a prop.

AddTodo = connect(
  null,
  null
)(AddTodo);

In fact, I can just remove all arguments here. The default behavior will be to not subscribe to this chore and to inject just the dispatch function as a prop.:

AddTodo = connect()(AddTodo);

So the AddTodo function would looks like:

let AddTodo = ({ dispatch }) => {
  let input;

  return (
    <div>
      <input ref={node => {
        input = node;
      }} />
      <button onClick={() => {
        dispatch({
          type: ‘ADD_TODO‘,
          id: nextTodoId++,
          text: input.value
        })
        input.value = ‘‘;
      }}>
        Add Todo
      </button>
    </div>
  );
};
AddTodo = connect()(AddTodo);

The AddTodo component that I declare accepts dispatch as a prop, but it doesn‘t know how to get this store. It just hopes that someone is going to pass the dispatch to it.

The connect code without any arguments is going to generate a container component that does not subscribe to this store. However, that will pass dispatch to the component that it wraps. In this case, it wraps my AddTodo component.

The second connect call returns the generated container component. I‘m assigning it to AddTodo. I‘m reassigning the let binding the second time.

------------------

Code:

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 { 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() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    );
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    const props = this.props;
    const { store } = this.context;
    const state = store.getState();

    return (
      <Link
        active={
          props.filter ===
          state.visibilityFilter
        }
        onClick={() =>
          store.dispatch({
            type: ‘SET_VISIBILITY_FILTER‘,
            filter: props.filter
          })
        }
      >
        {props.children}
      </Link>
    );
  }
}
FilterLink.contextTypes = {
  store: React.PropTypes.object
};

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 { connect } = ReactRedux;

let nextTodoId = 0;
let AddTodo = ({ dispatch }) => {
  let input;

  return (
    <div>
      <input ref={node => {
        input = node;
      }} />
      <button onClick={() => {
        dispatch({
          type: ‘ADD_TODO‘,
          id: nextTodoId++,
          text: input.value
        })
        input.value = ‘‘;
      }}>
        Add Todo
      </button>
    </div>
  );
};
AddTodo = connect()(AddTodo);

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
      );
  }
}

const mapStateToTodoListProps = (state) => {
  return {
    todos: getVisibleTodos(
      state.todos,
      state.visibilityFilter
    )
  };
};

const mapDispatchToTodoListProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch({
        type: ‘TOGGLE_TODO‘,
        id
      });
    }
  };
};

const VisibleTodoList = connect(
  mapStateToTodoListProps,
  mapDispatchToTodoListProps
)(TodoList);

const TodoApp = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
);

const { Provider } = ReactRedux;
const { createStore } = Redux;

ReactDOM.render(
  <Provider store={createStore(todoApp)}>
    <TodoApp />
  </Provider>,
  document.getElementById(‘root‘)
);
时间: 2024-11-16 13:28:02

[Redux] Generating Containers with connect() from React Redux (AddTodo)的相关文章

[Redux] Generating Containers with connect() from React Redux (FooterLink)

Code to be refactored: class FilterLink extends Component { componentDidMount() { const { store } = this.context; this.unsubscribe = store.subscribe(() => this.forceUpdate() ); } componentWillUnmount() { this.unsubscribe(); } render() { const props =

react redux 深入剖析--干货

## 技术栈: react + redux + webpack + react-router + ES6/7/8 + immutable ## 运行项目(nodejs 6.0+) ``` git clone https://github.com/bailicangdu/react-pxq.git cd react-pxq npm i 或者运行 yarn(推荐) npm start npm run build (发布)``` ## 说明 > 本项目主要用于理解 react 和 redux 的编译方

react+redux教程(五)异步、单一state树结构、componentWillReceiveProps

教程目录 react+redux教程(一)connect.applyMiddleware.thunk.webpackHotMiddleware react+redux教程(二)redux的单一状态树完全替代了react的状态机? react+redux教程(三)reduce().filter().map().some().every()....展开属性 react+redux教程(四)undo.devtools.router react+redux教程(五)异步.单一state树结构.compo

react+redux教程(四)undo、devtools、router

上节课,我们介绍了一些es6的新语法:react+redux教程(三)reduce().filter().map().some().every()....展开属性 今天我们通过解读redux-undo的官方示例代码来学习,在redux中使用撤销功能.devtools功能.以及router. 例子 这个例子是个计数器程序,包含计数器.右边的redux开发工具.还有一个路由(不过只有“/”这一个地址). 源代码: https://github.com/lewis617/myReact/tree/ma

react + redux 实现幻灯片

写在前面: 这一篇是我 使用scss + react + webpack + es6实现幻灯片 的进阶篇,效果请点我,将会使用上redux的基础用法,因为一开始没有理解好redux的用法,单纯看文档,实现Todo List效果.但却没有形成思路,当想改造成自己东西时,一脸懵逼,无从下手.后面动手整理思路,以下将会整理我的学习思路,如有出错,请各位指教. 首先 1.你有接触React 同时研究了 Redux,没有的话这里有不错的学习资源. Redux中文文档 Redux 指导与Redux 式编程 

react+redux渲染性能优化原理

大家都知道,react的一个痛点就是非父子关系的组件之间的通信,其官方文档对此也并不避讳: For communication between two components that don't have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillU

webpack+react+redux+es6

一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入门教程   redux middleware 详解   Redux研究 React 入门实例教程 webpack学习demo NPM 使用介绍 三.工程搭建 之前有写过 webpack+react+es6开发模式 ,文章里介绍了一些简单的配置,欢迎访问. 1.可以npm init, 创建一个新的工程

webpack+react+redux+es6开发模式

一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入门教程   redux middleware 详解   Redux研究 React 入门实例教程 webpack学习demo NPM 使用介绍 三.工程搭建 之前有写过 webpack+react+es6开发模式 ,文章里介绍了一些简单的配置,欢迎访问. 1.可以npm init, 创建一个新的工程

react + redux 完整的项目,同时写一下个人感悟

先附上项目源码地址和原文章地址:https://github.com/bailicangd... 做React需要会什么? react的功能其实很单一,主要负责渲染的功能,现有的框架,比如angular是一个大而全的框架,用了angular几乎就不需要用其他工具辅助配合,但是react不一样,他只负责ui渲染,想要做好一个项目,往往需要其他库和工具的配合,比如用redux来管理数据,react-router管理路由,react已经全面拥抱es6,所以es6也得掌握,webpack就算是不会配置也