Как предотвратить недетерминированное обновление c состояния в Redux? - PullRequest
2 голосов
/ 05 мая 2020

При работе с Redux очень важно поддерживать форму исходного состояния. Результаты / данные побочных эффектов, таких как вызов API, изменят форму состояния, поскольку мы не можем контролировать свойства. Например, рассмотрим это начальное состояние:

const book = {
  id: 0,
  name: 'something'
};

И обновление выполняется для него суб-редуктором книги следующим образом на основе данных API:

//receives `book` part of the state
const bookReducer = (state=book, action) => {
   switch(action.type) {
     case 'SET_BOOK': {
       return { ...action.payload };
     } default: 
       return state;
   }
}

Два сценария ios это могло произойти:

  1. Если данные, отправленные из API, - null,, то новое созданное состояние теперь {} в результате действия оператора распространения. Если некоторые части пользовательского интерфейса будут прослушивать часть состояния book, то она сломается. Возможно ли получить доступ к отдельным свойствам из данных API? В этом случае для свойств необходимо выполнить проверку на null / undefined. Есть ли более элегантное решение?

  2. В данных также могут быть дополнительные свойства, которые могут нас не интересовать. Возможно, использовать средство сопоставления объектов для фильтрации неиспользуемых свойств?

Как лучше всего обрабатывать такие сценарии ios и предотвращать недетерминированность состояния c? Пожалуйста, поделитесь своим опытом о том, как вы подошли к этому сценарию ios.

Ответы [ 6 ]

1 голос
/ 08 мая 2020

Только редуктор должен быть чистым / детерминированным c, а не все, что находится вне его. dispatch-call, чтобы редуктор всегда получал действительные данные.

Например, преобразователь может выглядеть так:

const createBook = (name) => {
  return async dispatch => {
   // suppose the api call gives back "uid" plus extra data
   const { uid, ...unneededData } = await myApi.setBook(name);

   // dispatch the data in the way the reducer expects it 
   dispatch({ type: 'SET_BOOK', id: uid, name });
  }
}

В приведенном выше примере вызов api дает мне uid, но нет name и куча лишних данных. Просто подготовьте данные перед их отправкой.

0 голосов
/ 15 мая 2020

Что я делаю, так это все мои logi c в моем методе действий и создаю редукторы для случаев, когда действие выполняется правильно, а еще одно для случаев, когда отклоняется. В выполненном редукторе я бы выполнил обычные инструкции, а в отклоненном редукторе я бы добавил данные в переменную с именем error, которая у меня всегда есть в моем состоянии, и использовал бы в интерфейсе, чтобы при необходимости показать сообщение об ошибке.

Пример

Это действие, которое создает дом путем отправки почтового запроса на мой api, который возвращает созданный объект или ошибку, если что-то пошло не так.

export const createHouse = houseData => {
  const URL = HTTP://EXAMPLE.URL

  return async dispatch => {
    try {
      const response = await axios.post(`${URL}`, houseData);
      const data = await response.data;
      dispatch({ type: "CREATE_HOUSE_DRAFT_FULFILLED", data });
    } catch (err) {
      dispatch({ type: "CREATE_HOUSE_DRAFT_REJECTED", data: err });
    }
  };
};

Тогда я бы У вас есть 2 метода редуктора для получения выполненного или отклоненного ответа, например:

        case 'CREATE_HOUSE_DRAFT_FULFILLED': {
            return {
                houses: [action.data, ...state.houses],
                house: action.data,
                houseCount: state.houseCount + 1,
                fetched: true,
                error: null
            };
        }
        case 'CREATE_HOUSE_DRAFT_REJECTED': {
            return {
                ...state,
                error: action.data.response.data,
                fetched: false,
                success: null
            };
        }

Надеюсь, это сработает для вас!

0 голосов
/ 14 мая 2020

Если значение поля из API не определено, преобразуйте его в null и сохраните, чтобы код не ломался и работал. Если API также предоставляет другие параметры, то деструктурируйте возвращаемый API объект и извлеките необходимые поля. Так что можно избежать хранения ненужных данных.

const bookReducer = (state=book, action) => {
   switch(action.type) {
     case 'SET_BOOK': {
        const {id, name, otherParam1, otherParam2} = action.payload;
        return {
          id: id || null,
          name: name || null,
          otherParam1,
          otherParam2
        }
     } default: 
       return state;
   }
}

Наличие значения null не приведет к повреждению кода, оно не отображает ничего лучше, чем undefined, которое нарушает код

Надеюсь, это вам поможет

0 голосов
/ 14 мая 2020

Ваш редуктор-редуктор c не должен беспокоиться об этом из-за его детерминированной c природы. Вы обрабатываете свой вызов api и обработку ответа в другом месте (преобразователь или компонент redux), а затем отправляете действие для установки вашего redux. Построение вашего примера:

book.reducer. js

const book = {
  id: 0,
  name: ''
};

const bookReducer = (state=book, action) => {
   switch(action.type) {
     case 'SET_BOOK': {
       return { ...action.payload };
     } default: 
       return state;
   }

book.actions. js

const setBook = (book) => ({
    type: SET_HEROES,
    payload: book
});

// thunk
const findBook = name => async dispatch => {
    const book = await bookService.findBook(name);

    if (book) {
        dispatch(setBook(book));
    }
};

book.service. js

const findBook = async (name) => {
    // Do your api call
    const bookResponse = axios.get(`${apiUrl}/book/search/${name}`);

    // Handle the response
    if (!bookResponse) {
      // Logic you do if book not found
      return null;
    }

    return {id: bookResponse.id, name: bookResponse.name};
}

Теперь в компоненте вы можете просто отправить вызов findBook

Component. js

    const Component = () => {
        const [search, setSearch] = useState('');
        const dispatch = useDispatch();

        const handleOnSearch = () => {
            dispatch(findBook(search));
        }

        return (
             <div>
                 <input value={search} onChange={(e) => setSearch(e.target.value)}/>
                 <button onClick={handleOnSearch}>Search</button>
             </div>
        );
    }
0 голосов
/ 12 мая 2020

В вашем редукторе вы можете сделать как показано ниже. Таким образом, будут обновлены только id и name, даже если в ответе есть другие ключи / значения. Также это гарантирует, что при получении нулевых значений эти значения не будут обновляться в состоянии. Надеюсь, это поможет в решении проблемы.

    //receives `book` part of the state
    const bookReducer = (state=book, action) => {
      const { type, payload } = action;
       switch(type) {
         case 'SET_BOOK': {
           return { 
             ...state,
             ...(payload.id && {id: payload.id}),
             ...(payload.name && {name: payload.name})
           };
         } default: 
           return state;
       }
    }

0 голосов
/ 09 мая 2020
• 1000 В вашем случае я бы проверил и достоверность данных, и сопоставил бы их с необходимым форматом.
  1. отправляйте 'SET_BOOK' только в том случае, если в ответе API есть как идентификатор, так и книга.
  2. по порядку чтобы избежать ненужных дополнительных свойств, вы всегда можете сопоставить свои данные const book = {id: apiData.id, book: apiData.book} перед отправкой.
...