JavaScript - funkcje Pure vs Impure

12

Przejrzałem definicje dwóch takich jak:

Czyste funkcje to takie, które nie próbują zmieniać swoich danych wejściowych i zawsze zwracają ten sam wynik dla tych samych danych wejściowych.

Przykład

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

A funkcja zanieczyszczona to taka, która zmienia własne dane wejściowe.

Przykład

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

Definicje i fragmenty kodu zaczerpnięte z oficjalnych dokumentów ReactJs .

Czy ktoś może mi teraz powiedzieć, jak mogę popełnić błędy w React / Redux , aby używać nieczystych funkcji tam, gdzie wymagane są czyste funkcje?

MuhammadUmarFarooq
źródło
4
Zanieczyszczone funkcje mają skutki uboczne. Coś w rodzaju window.getElementByIditp., Więc uruchomienie tej samej funkcji z tymi samymi parametrami może mieć różne wyniki w zależności od skutków ubocznych. W takim przypadku redux nie powiodło się.
Dominik

Odpowiedzi:

10

Reakcja i Reduxobie potrzebują czystych funkcji w połączeniu z niezmiennością do działania w przewidywalny sposób.

Jeśli nie zastosujesz się do tych dwóch rzeczy, aplikacja będzie zawierała błędy, z których najczęściej React/Reduxnie można śledzić zmian i nie można ich ponownie renderować po state/propwprowadzeniu zmian.

Jeśli chodzi o React, rozważ następujący przykład:

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();
}

Stan jest utrzymywany przez obiekt stanu, który tylko dodał właściwość. Ta aplikacja renderuje właściwość aplikacji. Nie zawsze powinien renderować stan, gdy coś się dzieje, ale powinien sprawdzać, czy nastąpiła zmiana w obiekcie stanu.

W ten sposób mamy funkcję efektów, pure functionktórej używamy, aby wpływać na nasz stan. Widzisz, że zwraca nowy stan, gdy stan ma zostać zmieniony, i zwraca ten sam stan, gdy modyfikacja nie jest wymagana.

Mamy również shouldUpdatefunkcję, która sprawdza za pomocą operatora ===, czy stary stan i nowy stan są takie same.

Aby popełnić błędy w zakresie React, możesz w rzeczywistości wykonać następujące czynności:

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

Możesz także popełniać błędy, ustawiając stan bezpośrednio i nie używając effectsfunkcji.

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

Powyższe nie powinno być wykonane i effectsnależy użyć tylko funkcji do aktualizacji stanu.

Pod względem React nazywamy effectsjako setState.

W przypadku Redux:

  1. combineReducersNarzędzie Redux sprawdza zmiany referencyjne.
  2. connectMetoda React-Redux generuje komponenty, które sprawdzają zmiany odniesienia zarówno dla stanu głównego, jak i zwracanych wartości z mapStatefunkcji, aby sprawdzić, czy zawinięty komponent rzeczywiście musi się ponownie renderować.
  3. Debugowanie w czasie wymaga, aby reduktor był pure functionsbez skutków ubocznych, aby można było poprawnie przeskakiwać między różnymi stanami.

Możesz łatwo złamać powyższe trzy, używając nieczystych funkcji jako reduktorów.

Poniżej zaczerpnięto bezpośrednio z dokumentów redux:

Nazywa się to reduktorem, ponieważ jest to typ funkcji, do której należy przekazać Array.prototype.reduce(reducer, ?initialValue).
Bardzo ważne jest, aby reduktor pozostał czysty. Rzeczy, których nigdy nie powinieneś robić wewnątrz reduktora:

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

Biorąc pod uwagę te same argumenty, powinien obliczyć następny stan i zwrócić go. Bez niespodzianek. Bez skutków ubocznych. Brak wywołań API. Bez mutacji. Tylko kalkulacja.

Utsav Patel
źródło
7

Mówiąc wprost, państwo nie może być zmutowane. Nowa instancja stanu powinna być zwracana za każdym razem, gdy zachodzi taka zmiana

Ten kod jest nieprawidłowy:

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

Ten kod, gdy został napisany jako czysta funkcja poniżej, Zwraca nową instancję tablicy, która nie modyfikuje samej tablicy. To jest powód, dla którego powinieneś używać biblioteki typu immmer do obsługi niezmienności

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
  }
}
muddassir
źródło
5

Możesz sprawić, że czyste funkcje będą nieczyste, dodając wywołania API lub pisząc kody, które powodują skutki uboczne.

Czyste funkcje powinny zawsze być trafne i zrozumiałe i nie powinny wymagać od ciebie odnoszenia 3 lub 4 innych funkcji, aby zrozumieć, co się dzieje.

// Pure Function
function USDtoEUR(USD, todayRate) {
  return USD * todayRate;
}

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

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

Nie należy tego robić . Wszystko, czego potrzebuje funkcja Connect lub funkcja reduktora, musi być dostarczone przez argument lub zapisane w ramach tej funkcji. Nigdy nie powinien dostać się z zewnątrz.

Puszkin
źródło