React & Redux

这两种技术的关键原则是什么?

React 是提供数据的视图库,能以高效、可预测的方式渲染视图。

Redux 是状态管理框架,可用于简化 APP 应用状态的管理。

在 React Redux app 应用中,通常可创建单一的 Redux store 来管理整个应用的状态。

React 组件仅订阅 store 中与其角色相关的数据, 可直接从 React 组件中分发 actions 以触发 store 对象的更新。

React 组件可以在本地管理自己的状态,但是对于复杂的应用来说,它的状态最好是用 Redux 保存在单一位置,有特定本地状态的独立组件例外。

当单个组件可能仅具有特定于其的本地状态时,算是例外。

最后一点是,Redux 没有内置的 React 支持,需要安装 react-redux包, 通过这个方式把 Redux 的 state 和 dispatch 作为 props 传给组件。

一、React 和 Redux

class DisplayMessages extends React.Component {
  // 添加下面这行代码
  constructor(props) {
    super(props);
    this.state = {
      input: '',
      messages: []
    }
  }
  // 添加上面这行代码
  render() {
    return <div />
  }
};

二、首先在本地管理状态

class DisplayMessages extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: '',
      messages: []
    }
  }
  // 在这里添加 handleChange() 和 submitMessage() 方法
  handleChange(event) {
    this.setState({
      input: event.target.value,
      messages: this.state.messages
    })
  }

  submitMessage() {
    this.setState({
      input: '',
      messages: [...this.state.messages, this.state.input]
    })
  }

  render() {
    return (
      <div>
        <h2>Type in a new Message:</h2>
        <input onChange={this.handleChange.bind(this)} value={this.state.input} />
        <button onClick={this.submitMessage.bind(this)}>Submit</button>
        <ul>
          {this.state.messages.map((x, i) => {
            return <li key={i}>{x}</li>
          })}
        </ul>
      </div>
    );
  }
};

三、提取状态逻辑给 Redux

完成 React 组件后,需要把在本地 state 执行的逻辑移到 Redux 中, 这是为小规模 React 应用添加 Redux 的第一步。

该应用的唯一功能是把用户的新消息添加到无序列表中。

// 定义 ADD、addMessage()、messageReducer() 并在这里存储:
const ADD = "ADD";
const addMessage = message => {
  return {
    type: ADD,
    message
  };
};

// 使用 ES6 默认参数给 'previous State' 参数一个初始值。
const messageReducer = (previousState = [], action) => {
  // 使用switch语句来布局reducer逻辑以响应不同的动作类型
  switch (action.type) {
    case ADD:
      // 使用 ES6 扩展运算符返回一个新数组,其中将新消息添加到 previousState
      return [...previousState, action.message];
      break;
    default:
      // 如果 Redux 存储的更新不是针对此特定状态,则要退回到默认情况。
      return previousState;
  }
};

const store = Redux.createStore(messageReducer);

四、使用 Provider 链接 Redux 和 React

在上一步中,创建了 Redux store 和 action,分别用于处理消息数组和添加新消息。

下一步要为 React 提供访问 Redux store 及发起更新所需的 actions。 react-redux 包可帮助我们完成这些任务。

react-redux 提供的 API 有两个关键的功能:Providerconnect

Provider 是 wrapper 组件, 它允许访问整个组件树中的 Redux store 及 dispatch(分发)方法。

Provider 需要两个 props:Redux store 和 App 应用的子组件。

// Redux:
const ADD = 'ADD';

const addMessage = (message) => {
  return {
    type: ADD,
    message
  }
};

const messageReducer = (state = [], action) => {
  switch (action.type) {
    case ADD:
      return [
        ...state,
        action.message
      ];
    default:
      return state;
  }
};



const store = Redux.createStore(messageReducer);

// React:

class DisplayMessages extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: '',
      messages: []
    }
    this.handleChange = this.handleChange.bind(this);
    this.submitMessage = this.submitMessage.bind(this);
  }
  handleChange(event) {
    this.setState({
      input: event.target.value
    });
  }
  submitMessage() {
    this.setState((state) => {
      const currentMessage = state.input;
      return {
        input: '',
        messages: state.messages.concat(currentMessage)
      };
    });
  }
  render() {
    return (
      <div>
        <h2>Type in a new Message:</h2>
        <input
          value={this.state.input}
          onChange={this.handleChange} /><br />
        <button onClick={this.submitMessage}>Submit</button>
        <ul>
          {this.state.messages.map((message, idx) => {
            return (
              <li key={idx}>{message}</li>
            )
          })
          }
        </ul>
      </div>
    );
  }
};

const Provider = ReactRedux.Provider;

class AppWrapper extends React.Component {
  // 在这一行下面渲染 Provider
  render() {
    return (
      <Provider store={store}>
        <DisplayMessages />
      </Provider>
    );
  }
};

五、映射 State 到 Props

Provider 可向 React 组件提供 state 和 dispatch ,但必须确切地指定所需要的 state 和 actions, 以确保每个组件只能访问所需的 state。

完成这个任务,需要创建两个函数:mapStateToProps()、mapDispatchToProps()。

在这两个函数中,声明 state 中函数所要访问的部分及需要 dispatch 的创建 action 的函数。

注意: 在幕后,React Redux 用 store.subscribe() 方法来实现 mapStateToProps()。

const state = [];

const mapStateToProps = (state)=>{
  return {
    messages: state
  }
}

六、映射 Dispatch 到 Props

mapDispatchToProps() 函数可为 React 组件提供特定的创建 action 的函数,以便组件可 dispatch actions,从而更改 Redux store 中的数据。

该函数的结构跟上一步中的mapStateToProps()函数相似, 它返回一个对象,把 dispatch actions 映射到属性名上,该属性名成为props。

然而,每个属性都返回一个用 action creator 及与 action 相关的所有数据调用 dispatch 的函数,而不是返回 state 的一部分。

可以访问 dispatch,因为在定义函数时,我们以参数形式把它传入 mapDispatchToProps() 了,这跟 state 传入 mapStateToProps() 是一样的。

在幕后,React Redux 用 Redux 的 store.dispatch() 来管理这些含 mapDispatchToProps() 的dispatches, 这跟它使用 store.subscribe() 来订阅映射到 state 的组件的方式类似。

const addMessage = (message) => {
  return {
    type: 'ADD',
    message: message
  }
};

const mapDispatchToProps = (dispatch) => {
  return {
    submitNewMessage: (message) => {
      dispatch(addMessage(message))
    }
  }
}

七、连接 Redux 和 React

已经写了mapStateToProps()、mapDispatchToProps() 两个函数,现在可以用它们来把 state 和 dispatch 映射到 React 组件的 props 了。

React Redux 的 connect 方法可以完成这个任务。

此方法有 mapStateToProps()、mapDispatchToProps() 两个可选参数, 它们是可选的,原因是你的组件可能仅需要访问 state 但不需要分发任何 actions,反之亦然。

const addMessage = (message) => {
  return {
    type: 'ADD',
    message: message
  }
};

const mapStateToProps = (state) => {
  return {
    messages: state
  }
};

const mapDispatchToProps = (dispatch) => {
  return {
    submitNewMessage: (message) => {
      dispatch(addMessage(message));
    }
  }
};

class Presentational extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <h3>This is a Presentational Component</h3>
  }
};

const connect = ReactRedux.connect;
// 添加这行下面的代码
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(Presentational)

八、将 Redux 链接到 Messages App

在上一步,连接到 Redux 的组件命名为 Presentational,这个命名不是任意的, 这样的术语通常是指未直接连接到 Redux 的 React 组件, 它们只负责执行接收 props 的函数来实现 UI 的呈现。

相比之下,容器组件用来连接到 Redux 上。 这些组件通常负责把 actions 分派给 store,且经常给子组件传入 store state 属性。

// Redux:
const ADD = 'ADD';

const addMessage = (message) => {
  return {
    type: ADD,
    message: message
  }
};

const messageReducer = (state = [], action) => {
  switch (action.type) {
    case ADD:
      return [
        ...state,
        action.message
      ];
    default:
      return state;
  }
};

const store = Redux.createStore(messageReducer);

// React:
class Presentational extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: '',
      messages: []
    }
    this.handleChange = this.handleChange.bind(this);
    this.submitMessage = this.submitMessage.bind(this);
  }
  handleChange(event) {
    this.setState({
      input: event.target.value
    });
  }
  submitMessage() {
    this.setState((state) => {
      const currentMessage = state.input;
      return {
        input: '',
        messages: state.messages.concat(currentMessage)
      };
    });
  }
  render() {
    return (
      <div>
        <h2>Type in a new Message:</h2>
        <input
          value={this.state.input}
          onChange={this.handleChange} /><br />
        <button onClick={this.submitMessage}>Submit</button>
        <ul>
          {this.state.messages.map((message, idx) => {
            return (
              <li key={idx}>{message}</li>
            )
          })
          }
        </ul>
      </div>
    );
  }
};

// React-Redux:
const mapStateToProps = (state) => {
  return { messages: state }
};

const mapDispatchToProps = (dispatch) => {
  return {
    submitNewMessage: (newMessage) => {
      dispatch(addMessage(newMessage))
    }
  }
};

const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;

// 在这里定义 Container 组件:
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational)

class AppWrapper extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    // 完成 return 语句
    return (
      <Provider store={store}>
        <Container />
      </Provider>
    );
  }
};

九、将局部状态提取到 Redux 中

现在有了连接好的 Redux,还要从Presentational组件中提取状态管理到 Redux, 目前,已连接 Redux,但正在 Presentational 组件中本地处理状态。

// Redux:
const ADD = 'ADD';

const addMessage = (message) => {
  return {
    type: ADD,
    message: message
  }
};

const messageReducer = (state = [], action) => {
  switch (action.type) {
    case ADD:
      return [
        ...state,
        action.message
      ];
    default:
      return state;
  }
};

const store = Redux.createStore(messageReducer);

// React:
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;

// 修改这行下面的代码
class Presentational extends React.Component {
  constructor(props) {
    super(props);

    // Remove property 'messages' from Presentational's local state
    this.state = {
      input: ''
    }
    this.handleChange = this.handleChange.bind(this);
    this.submitMessage = this.submitMessage.bind(this);
  }
  handleChange(event) {
    this.setState({
      input: event.target.value
    });
  }
  submitMessage() {

    // Call 'submitNewMessage', which has been mapped to Presentational's props, with a new message;
    // meanwhile, remove the 'messages' property from the object returned by this.setState().
    this.props.submitNewMessage(this.state.input);
    this.setState({
      input: ''
    });
  }
  render() {
    return (
      <div>
        <h2>Type in a new Message:</h2>
        <input
          value={this.state.input}
          onChange={this.handleChange} /><br />
        <button onClick={this.submitMessage}>Submit</button>
        <ul>
          {/* The messages state is mapped to Presentational's props; therefore, when rendering,
               you should access the messages state through props, instead of Presentational's
               local state. */}
          {this.props.messages.map((message, idx) => {
            return (
              <li key={idx}>{message}</li>
            )
          })
          }
        </ul>
      </div>
    );
  }
};
// 修改这行上面的代码

const mapStateToProps = (state) => {
  return { messages: state }
};

const mapDispatchToProps = (dispatch) => {
  return {
    submitNewMessage: (message) => {
      dispatch(addMessage(message))
    }
  }
};

const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);

class AppWrapper extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <Container />
      </Provider>
    );
  }
};