【问题标题】:ReactJS/Redux - Pure vs Impure Javascript functions?ReactJS/Redux - Pure vs Impure Javascript 函数?
【发布时间】:2025-12-11 00:10:01
【问题描述】:

我已经浏览了 ReactJs Official DocsPure 和 Impure Javascript 函数的定义。

纯函数是不尝试更改其输入,并且始终为相同输入返回相同结果的函数。

示例

function sum(a, b) {
  return a + b;
}

不纯函数是一种会改变自己的输入的函数。

示例

function withdraw(account, amount) {
  account.total -= amount;
}

现在,谁能告诉我,在需要纯函数的 React/Redux 中,我怎么会错误地使函数不纯?

【问题讨论】:

  • 不纯函数有副作用。像window.getElementById 之类的东西,因此使用相同的参数运行相同的函数可能会产生不同的结果,具体取决于副作用。这就是 redux 会失败的地方。

标签: javascript reactjs redux


【解决方案1】:

React 和Redux 都需要纯函数和不变性才能以可预测的方式运行。

如果您不遵循这两点,您的应用就会出现错误,最常见的是React/Redux 无法跟踪更改,并且当您的state/prop 更改时无法重新渲染。

就 React 而言,考虑以下示例:

let state = {
    add: 0,
}

function render() {
    //...
}
//pure function
function effects(state,action) {
//following immutability while updating state, not directly mutating the state.
    if(action == 'addTen') {
        return {...state, add: state.add + 10} 
    }
    return state;
}

function shouldUpdate(s) {
    if(s === state){
        return false
    }
    return true
}

state = effects(state, 'addTen')if(shouldUpdate(state)) {
    render();
}

状态由仅具有附加属性的状态对象持有。此应用呈现应用属性。它不应该总是在发生任何事情时呈现状态,而是应该检查状态对象中是否发生了变化。

像这样,我们有一个效果函数,一个pure function,我们用它来影响我们的状态。您会看到,当状态要更改时它会返回一个新状态,而当不需要修改时它会返回相同的状态。

我们还有一个 shouldUpdate 函数,它使用 === 运算符检查旧状态和新状态是否相同。

要在 React 方面犯错,您实际上可以执行以下操作:

function effects(state,action) {

  doRandom(); // effects should only be called for updating state.
             // Doing any other stuff here would make effects impure.

    if(action == 'addTen') {
        return {...state, add: state.add + 10}
    }
    return state;
}

你也可以通过直接设置状态而不使用effects函数来出错。

function doMistake(newValue) {
    this.state = newValue
}

以上不应该做,只应该使用effects函数来更新状态。

在 React 方面,我们将 effects 称为 setState

对于 Redux:

  1. Redux 的 combineReducers 实用程序检查引用更改。
  2. React-Redux 的 connect 方法生成组件,这些组件检查根状态和 mapState 函数的返回值的引用更改,以查看被包装的组件是否真的需要重新渲染。
  3. 时间穿梭调试要求reducer为pure functions且无副作用,这样才能在不同状态之间正确跳转。

使用不纯函数作为reducer,很容易违反上述三点。

以下内容直接取自 redux 文档:

之所以称为reducer,是因为它是您将传递给Array.prototype.reduce(reducer, ?initialValue) 的函数类型。
减速器保持纯净是非常重要的。在 reducer 中你不应该做的事情:

Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().

给定相同的参数,它应该计算下一个状态并返回它。没有惊喜。无副作用。没有 API 调用。没有突变。只是一个计算。

【讨论】:

    【解决方案2】:

    简单地说,状态不能改变。每次发生变化时都应该返回一个新的状态实例,所以

    此代码不正确:

    const initialStates = {    
      items: ['item1']
    }
    
    export const ItemMaster = (state = initialStates, action) => {    
      switch (action.type) {
        case TYPES.ADD_ITEM:            
        {
            state.items.push(action.item)
            return state
        }
        default:
          return state
      }
    }
    

    这段代码写成下面的纯函数时,它返回一个新的数组实例,它不会修改实际数组本身。这就是你应该使用像 immer 这样的库来处理不变性的原因

    const initialStates = { 
      items: ['item1']
    }
    
    export const ItemMaster = (state = initialStates, action) => {    
      switch (action.type) {
        case TYPES.ADD_ITEM:            
        {
    
            state = {...state,items:state.items.concat(action.item)}
            return state
        }
        default:
          return state
      }
    }
    

    【讨论】:

      【解决方案3】:

      您可以通过添加 API 调用或编写导致副作用的代码来使纯函数不纯。

      纯函数应始终准确且不言自明,不应要求您参考 3 或 4 个其他函数来了解发生了什么。

      // Pure Function
      function USDtoEUR(USD, todayRate) {
        return USD * todayRate;
      }
      
      // Impure Function 
      function USDtoEUR(USD) {
        const todayRate = getTodayRate();
        return USD * todayRate;
      }
      

      在 React / Redux 的情况下

      const mapState = async state => {
        const { data } = await whatDoINeed()
      
        let mappedState = {}
      
        if (data.needDolphin) {
          mappedState.dolphin = state.dolphin
        }
      
        if (data.needShark) {
          mappedState.shark= state.shark
        }
      
        return mappedState;
      }
      
      // Or for Redux Reducer
      // Bad
      {
        setData: (state, payload) => {
         const set = whatToSet()
         return {
           ...state,
           set.dolphin ? ...{ dolphin: payload.dolphin } : ...{},
           set.shark ? ...{ shark : payload.shark } : ...{},
         }
        }
      }
      
      // Good
      {
        setData: (state, payload) => {
         return {
           ...state,
           // Just send only the things need
           // to be sent
           ...payload
         }
        }
      }
      
      

      不应这样做。 connect 函数或 reducer 函数所需的一切都必须通过参数提供或写入其函数中。它不应该从外面得到。

      【讨论】: