JavaScript-純粋関数と不純関数


12

私は次のような2つの定義を通過しました:

純粋な関数とは、入力を変更しようとせず、常に同じ入力に対して同じ結果を返す関数です。

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

そして不純関数はそれ自身の入力を変更するものです。

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

ReactJの公式ドキュメントから取得した定義とコードスニペット。

純粋な関数が必要な場所で不純な関数を使用するために、React / Reduxでいくつかの間違いをする方法を教えてください。


4
不純な関数には副作用があります。何かのようにwindow.getElementById同じパラメータで同じ機能を実行しているので、などは、副作用に応じて、異なる結果を持つことができます。これは、reduxが失敗する場所です。
ドミニク

回答:


10

ReactとReduxその両方は、予測可能な方法で実行するために、不変性を備えた純粋な関数を必要とします。

これら2つを守らないと、アプリにバグが発生します。最も一般的なのは、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. タイムトラベルデバッグでは、pure functions異なる状態間を正しくジャンプできるように、レデューサーに副作用がないことが必要です。

不純な関数をリデューサーとして使用すると、上記の3つに簡単に違反する可能性があります。

以下はredux docsから直接取られたものです:

これは、渡す関数のタイプであるため、レデューサーと呼ばれArray.prototype.reduce(reducer, ?initialValue)ます。
減速機が純粋であることは非常に重要です。あなたが減速機の中で決してしてはいけないこと:

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

同じ引数があれば、次の状態を計算して返す必要があります。驚く様な事じゃない。副作用はありません。API呼び出しはありません。突然変異なし。単なる計算。


7

簡単に言うと、状態を変更することはできません。変更があるたびに状態の新しいインスタンスを返す必要があるため、

このコードは正しくありません:

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

このコードは、以下の純粋な関数として記述されると、実際の配列自体を変更しない配列の新しいインスタンスを返します。これがイマーのようなライブラリを使用して不変性を処理する必要がある理由です

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

5

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

これはすべきではありません。接続関数またはレデューサー関数が必要とするすべてのものは、引数を介して提供するか、またはその関数内に記述する必要があります。外から出てはいけません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.