【译】Redux 中的Reducer 函数

709 阅读5分钟

转载自 Kingsley Silas. Understanding How Reducers are Used in Redux. Oct 24, 2019

reducer是一个决定应用程序状态变化函数。它使用它接收到的动作来确定这种变化。我们有一些工具,比如Redux,可以帮助管理应用程序在单一存储中的状态变化,使它们的行为一致。

为什么我们在谈论reducers的时候要提到Redux?Redux在很大程度上依赖于reducer函数,这些函数利用上一个状态和一个动作来执行下一个状态。

我们将在这篇文章中直接关注reducer。我们的目标是熟悉使用reducer函数,这样我们就可以看到它是如何用来更新应用程序的状态的--并最终理解它们在状态管理器中扮演的角色,比如Redux。

"状态"是什么意思

状态变化是一个基于用户的交互,甚至是类似网络请求的东西。如果应用程序的状态是由Redux管理的,那么变化就发生在reducer函数中--这是唯一发生状态变化的地方。reducer函数利用应用程序的初始状态 initial state和一些叫做动作 action的东西,来决定新的状态 new state会是什么样子。

如果我们在数学课上,我们可以说。

initial state + action = new state

从实际的reducer函数来看,是这样的。

const contactReducer = (state = initialState, action) => {
  // Do something
}

我们从哪里获得初始状态和行为?这些都是我们要定义的东西

参数 state

传递给reducer函数的参数state必须是应用程序的当前状态。在这个例子中,我们把它称为initialState,因为它将是第一个(也是当前)状态,而在它之前不会有任何东西。

contactReducer(initialState, action)

比方说,我们应用的初始状态是一个空的联系人列表,我们的操作是向列表中添加一个新的联系人。

const initialState = {
  contacts: []
}

这就创建了我们的InitialState,它等于我们在reducer数中需要的参数state

参数 action

一个action是一个包含两个键及其值的对象。在reducer中发生的状态更新总是依赖于action.type的值。在这个场景中,我们要演示的是当用户试图创建一个新的联系人时发生的事情。所以,让我们把action.type定义为NEW_CONTACT

const action = {
  type: 'NEW_CONTACT',
  name: 'John Doe',
  location: 'Lagos Nigeria',
  email: 'johndoe@example.com'
}

通常有一个payload值,包含用户发送的内容,将用于更新应用程序的状态。需要注意的是,action.type 是必需的,但 action.payload 是可选的。使用payload为action对象的外观带来了一定程度的结构性。

更新状态

状态的意思是,它是不可变的,也就是说不应该直接改变它。要创建一个更新的状态,我们可以利用Object.assign或者选择spread操作符

Object.assign

const contactReducer = (state, action) => {
  switch (action.type) {
    case 'NEW_CONTACT':
    return Object.assign({}, state, {
      contacts: [
        ...state.contacts,
        action.payload
      ]
    })
    default:
      return state
  }
}

在上面的例子中,我们使用了Object.assign()来确保我们不直接改变状态值。相反,它允许我们返回一个新的对象,这个对象充满了传递给它的状态和用户发送的有效载荷。

要使用Object.assign(),重要的是第一个参数是一个空对象。传递状态作为第一个参数会导致它被突变,这也是我们要避免的,以保持一致性。

spread 操作符

object.assign()的替代方法是使用spread操作符,像这样。

const contactReducer = (state, action) => {
  switch (action.type) {
    case 'NEW_CONTACT':
    return {
        ...state, contacts:
        [...state.contacts, action.payload]
    }
    default:
      return state
  }
}

这确保了在我们将新项目追加到底部时,入库状态保持不变。

使用switch语句

前面,我们注意到,发生的更新取决于action.type的值。switch语句根据action.type的值,有条件地决定我们要处理的更新类型。

这意味着一个典型的reducer会是这样的。

const addContact = (state, action) => {
  switch (action.type) {
    case 'NEW_CONTACT':
    return {
        ...state, contacts:
        [...state.contacts, action.payload]
    }
    case 'UPDATE_CONTACT':
      return {
        // Handle contact update
      }
    case 'DELETE_CONTACT':
      return {
        // Handle contact delete
      }
    case 'EMPTY_CONTACT_LIST':
      return {
        // Handle contact list
      }
    default:
      return state
  }
}

重要的是,当action对象中指定的 "action.type "的值与reducer中的值不匹配时,我们要返回我们的 "default "状态--比如说,如果由于某些未知的原因,action看起来像这样。

const action = {
  type: 'UPDATE_USER_AGE',
  payload: {
    age: 19
  }
}

由于我们没有这种动作类型,所以我们希望返回状态中的内容(应用程序的当前状态)来代替。总之我们不确定用户此刻想达到什么目的。

把所有东西放在一起

下面是我在React中实现reducer函数的一个简单例子。

const initialState = {
  contacts: [{
    name: 'Vic Henry',
    age: 30
  }]
};

const contactReducer = (state = initialState, action) => {
  switch (action.type) {
    case "NEW_CONTACT":
      return Object.assign({}, state, {
        contacts: [...state.contacts, action.payload]
      });
    default:
      return state;
  }
};

class App extends React.Component {
  constructor(props) {
    super(props);
    this.name = React.createRef();
    this.age = React.createRef();

    this.state = initialState;
  }

  handleSubmit = e => {
    e.preventDefault();
    const action = {
      type: "NEW_CONTACT",
      payload: {
        name: this.name.current.value,
        age: this.age.current.value
      }
    };
    const newState = contactReducer(this.state, action);
    this.setState(newState);
  };

  render() {
    const { contacts } = this.state;
    return (
      <div className="box">
        <div className="content">
          <pre>{JSON.stringify(this.state, null, 2)}</pre> 
        </div>
        
        <div className="field">
          <form onSubmit={this.handleSubmit}>
            <div className="control">
              <input className="input" placeholder="Full Name" type="text" ref={this.name} />
            </div>
            <div className="control">
              <input className="input" placeholder="Age" type="number" ref={this.age} />
            </div>
            <div>
              <button type="submit" className="button">Submit</button>
            </div>
          </form>
        </div>
      </div>
    );
  }
}


ReactDOM.render(
  <App />,
  document.getElementById('root')
);

你可以看到,我没有使用Redux,但这与Redux使用reducer来存储和更新状态变化的方式非常相同。主要的状态更新发生在reducer函数中,它返回的值设置了应用程序的更新状态。

想试一试吗?你可以扩展reducer函数,让用户更新联系人的年龄。我想在评论区看看你想到了什么!

了解Redux中reducer所扮演的角色,应该能让你更好地理解引擎下面发生的事情。如果你有兴趣阅读更多关于在Redux中使用reducer的内容,可以查看官方文档