[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')
);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
react-redux是一个流行的用于在React应用中管理全局状态的库。ReduxJavaScript中一个用于状态管理的工具,它通过将状态存储在单一的全局状态树中,并使用纯函数来处理状态的变化,使得状态管理变得简单而可预测。 在React中使用Redux,通常需要使用react-redux库提供的connect函数。connect函数是一个高阶组件,用于连接React组件和Redux的状态管理机制。 connect函数的基本用法是将React组件作为参数传递给connect,并在返回的函数中配置所需的状态和动作。它接受两个参数:mapStateToProps和mapDispatchToProps。 mapStateToProps是一个函数,用于从Redux的全局状态树中选择所需的状态,并作为React组件的属性传递给组件。这样,组件可以通过props访问到所需的状态,并将其渲染到页面中。 mapDispatchToProps是一个函数,用于将Redux的动作绑定到组件的props上。这样,组件可以通过调用props中的方法来触发Redux中定义的动作。 connect函数还可以接收一个可选的第三个参数,用来配置其他属性,比如在组件未连接到Redux时是否抛出警告。 使用connect函数后,就可以将Redux的状态和动作与React组件连接起来,并使其能够访问和操作全局状态。这样,组件就可以根据状态变化来更新UI,并通过触发动作来改变全局状态。 总而言之,react-reduxconnect函数提供了一种简单而方便的方式来连接React组件与Redux的状态管理机制,使得在React应用中管理全局状态变得更加容易和可维护。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值