我必须在Redux Reducer中返回状态吗?

时间:2020-06-10 20:57:30

标签: reactjs redux react-redux

我无法解决redux-reducer在JavaScript中的工作方式。

以下是我正在使用的示例:

<button className="btn btn-outline-success my-2 my-sm-0"
  type="submit"
  onClick={this.logout}
>
  <a href="/">Log Out</a>
</button>

对于该减速器,其中哪一个假设是正确的?

A)触发GET_PROFILE时,它将返回“ ... state”作为初始状态,以便redux开发人员工具可以比较状态。

B)触发GET_PROFILE时,实际上只是将值填充到我未明确列出的对象中。就像返回中的样子:

const intitialState = {
  profile: null,
  profiles: [],
  repos: [],
  loading: true,
  error: {},
};

export default function (state = intitialState, action) {
  const { type, payload } = action;

  switch (type) {
    case GET_PROFILE:
      return {
        ...state,
        profile: payload,
        loading: false,
      };
    case PROFILE_ERROR:
      return {
        ...state,
        error: payload,
        loading: false,
      };
    default:
      return state;
  }
}

如果A或B为真,那么如果我不返回... state作为第一个状态,会发生什么,这会弄乱其他对象吗?

编辑:它到底返回到什么地方?

3 个答案:

答案 0 :(得分:2)

我会说B是真的。

如果不返回状态,则处于未返回状态的数据将丢失。


函数createStore为您提供了5个core functions的存储对象。

我认为以下三个最显着的功能是从商店中返回的:

  • 订阅功能-用于订阅商店。此函数返回一个unsubscribe函数以取消订阅。
    • 如果您使用的是react-redux,则此订阅/取消订阅逻辑为 已经使用connect函数或 useSelector钩子。
  • getState 函数-用于获取当前状态。
    • 如果您使用的是react-redux,也可以在connect函数或useSelector挂钩中处理。
  • 调度功能-用于对全局状态进行更新。
    • 如果您使用的是react-redux,则在connect内部处理 函数或useDispatch钩子。

不从减速器返回状态副本将导致您丢失未返回的状态。要了解原因,请查看商店的dispatch函数。


在商店的dispatch函数内部,您将看到reassigned的变量simply returns是从减速器(或组合减速器)返回的内容。

商店source codecurrentState变量中的getState函数。始终从此currentState函数中检索当前状态。


因此,当您分派操作时,状态树将使用从化简器返回的内容进行更新。


这是一个小例子,说明当您从redux reducer返回状态与不返回状态时会发生什么情况。

您可以运行并重新运行该代码段。蓝色按钮将向getState发送返回状态的副本。黄色按钮调度一个操作GET_PROFILE,该操作返回与GET_PROFILE_NO_STATE_COPY_RETURNED相同的对象,但没有状态副本GET_PROFILE

实际上,一旦您点击...state,您将需要重新运行该代码段,因为未指定的状态属性已完全消失:)

redux状态将根据化简器返回的内容进行更新。

我从您的问题中使用了减速器:

GET_PROFILE_NO_STATE_COPY_RETURNED
const createStore = (reducer, preloadedState, enhancer) => {
  if (typeof enhancer !== 'undefined') {
    return enhancer(createStore)(reducer, preloadedState);
  }
  let currentState = preloadedState;
  let listeners = [];

  const getState = () => currentState;

  const subscribe = listener => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener);
    };
  };

  // The returned from the reducer
  // inside the dispatch function 
  // will be the new state.
  // https://github.com/reduxjs/redux/blob/master/src/createStore.ts#L246
  const dispatch = action => {
    currentState = reducer(currentState, action);
    listeners.forEach(l => l());
  };

  // initialize state
  dispatch({});

  return {
    getState,
    subscribe,
    dispatch,
  };
};

const initialState = {
  profile: null,
  profiles: [],
  repos: [],
  loading: true,
  error: {},
};

// action type constants
const GET_PROFILE = 'GET_PROFILE';
const PROFILE_ERROR = 'PROFILE_ERROR';
const GET_PROFILE_NO_STATE_COPY_RETURNED = 'GET_PROFILE_NO_STATE_COPY_RETURNED';

// reducer
const reducer = (state = initialState, {
  type,
  payload
}) => {
  switch (type) {
    case GET_PROFILE:
      return {
        ...state,
        profile: payload,
        loading: false,
      };
    case GET_PROFILE_NO_STATE_COPY_RETURNED:
      return {
        profile: payload,
        loading: false,
      };
    case PROFILE_ERROR:
      return {
        ...state,
        error: payload,
        loading: false,
      };
    default:
      return state;
  }
};

const store = createStore(reducer);

const render = () => {
  const html = `
    <p>State</p>
    <pre><code>${JSON.stringify(store.getState(), null, 4)}</code></pre>
`;
  document.getElementById('app').innerHTML = html;
};

store.subscribe(render);

render();

document.getElementById('get-profile').addEventListener('click', () => {
  const firstName = document.getElementById('firstName').value;
  const lastName = document.getElementById('lastName').value;
  store.dispatch({
    type: GET_PROFILE,
    payload: {
      firstName,
      lastName,
    },
  });
});

document
  .getElementById('get-profile-no-state-returned')
  .addEventListener('click', () => {
    const firstName = document.getElementById('firstName').value;
    const lastName = document.getElementById('lastName').value;
    store.dispatch({
      type: GET_PROFILE_NO_STATE_COPY_RETURNED,
      payload: {
        firstName,
        lastName,
      },
    });
  });


因此,当您调度动作时,reducer返回的值将成为您的新状态。

答案 1 :(得分:1)

[B]是最正确的答案,但并不完全正确。

在每个事件(或操作类型)处,状态都将被替换。 initialState 仅与第一步有关。

如以下示例所示,当触发ADD_RECIPIENT时,将显示以下行:

recipients: [...state.recipients, recipient]

表示state.recipients是其自身的串联加上要添加的新“收件人”。 与以下过程相同:

return {
    ...state,
    recipients: [...state.recipients, recipient]
}

状态由自身的串联代替。


示例:

const initialState = {
  selectedRecipient: '',
  recipients: []
};

export default function (state = initialState, action) {
  switch (action.type) {
      case LOAD_RECIPIENTS: {
          const { recipients } = action.payload;
          return {
              ...state,
              selectedRecipient: recipients[0],
              recipients: recipients
          };
      }
      case ADD_RECIPIENT: {
          const { recipient } = action.payload;
          return {
              ...state,
              recipients: [...state.recipients, recipient]
          };
      }
...
...

答案 2 :(得分:0)

它的作用类似于B选项。状态将用新值更新(正在使用传播运算符)。然后,将新状态发送到反应(返回将以这种方式工作)反应检查是否有任何更改,并在需要时更新DOM。模型非常简化,但接近真实。