如何从路径字符串中改变Redux状态?

问题描述 投票:0回答:1

我的初始状态是这样的。

const initialState = {
  array: [
    {
      key: "value",
      obj: {
        key1: "value",
        key2: "value",
      },
      array: [
        {
          key: "value",
          obj: {
            key1: "value",
            key2: "value",
          },
        }
      ]
    },
    {
      key: "value",
      obj: {
        key1: "value",
        key2: "value",
      },
    },
    {
      key: "value",
      obj: {
        key1: "value",
        key2: "value",
      },
    },
  ],
  path: "",
  value: ""
};

Reducer:

export const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "SET_PATH":
      return {
        ...state,
        path: action.path
      };

    case "SET_NEW_VALUE":
      return {
        ...state,
        newValue: action.value
      };

    case "SET_NEW_BUILD":
      //What next?

    default:
      return state
  }
};

Action creators:

const setPath = (path) => ({type: "SET_PATH", path});
const setNewValue = (value) => ({type: "SET_NEW_VALUE", value});
const setNewBuild = (path, value) => ({type: "SET_NEW_BUILD", path, value});

我需要用一个路径字符串和新的值来改变这个状态。

dispatch(setNewBuild("array[0].obj.key1", "newValue");

而且这个值可以有这样的形式 "obj:{key1: "newValue", key2: "newValue"}"这样就会创建一个新的对象。

我怎样才能做到这一点?

javascript reactjs redux
1个回答
0
投票

下面是一个使用 设置 帮助者。

const REMOVE = () => REMOVE;
//helper to get state values
const get = (object, path, defaultValue) => {
  const recur = (current, path, defaultValue) => {
    if (current === undefined) {
      return defaultValue;
    }
    if (path.length === 0) {
      return current;
    }
    return recur(
      current[path[0]],
      path.slice(1),
      defaultValue
    );
  };
  return recur(object, path, defaultValue);
};
//helper to set state values
const set = (object, path, callback) => {
  const setKey = (current, key, value) => {
    if (Array.isArray(current)) {
      return value === REMOVE
        ? current.filter((_, i) => key !== i)
        : current.map((c, i) => (i === key ? value : c));
    }
    return value === REMOVE
      ? Object.entries(current).reduce((result, [k, v]) => {
          if (k !== key) {
            result[k] = v;
          }
          return result;
        }, {})
      : { ...current, [key]: value };
  };
  const recur = (current, path, newValue) => {
    if (path.length === 1) {
      return setKey(current, path[0], newValue);
    }
    return setKey(
      current,
      path[0],
      recur(current[path[0]], path.slice(1), newValue)
    );
  };
  const oldValue = get(object, path);
  const newValue = callback(oldValue);
  if (oldValue === newValue) {
    return object;
  }
  return recur(object, path, newValue);
};
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore } = Redux;

//action
const setNewBuild = (path, value) => ({
  type: 'SET_NEW_BUILD',
  path,
  value,
});
const initialState = {
  array: [
    {
      key: 'value',
      obj: {
        key1: 'value',
        key2: 'value',
      },
    },
  ],
  path: '',
  value: '',
};
const reducer = (state = initialState, action) => {
  const { type } = action;
  if (type === 'SET_NEW_BUILD') {
    const { path, value } = action;
    return set(state, path, () => value);
  }
  return state;
};
const store = createStore(
  reducer,
  initialState,
  window.__REDUX_DEVTOOLS_EXTENSION__ &&
    window.__REDUX_DEVTOOLS_EXTENSION__()
);
const App = () => {
  const state = useSelector((x) => x);
  const dispatch = useDispatch();
  return (
    <div>
      <button
        onClick={() =>
          dispatch(
            setNewBuild(
              ['array', 0, 'obj', 'key1'],
              'new value key 1'
            )
          )
        }
      >
        change array 0 obj key1
      </button>
      <button
        onClick={() =>
          dispatch(
            setNewBuild(['array', 0, 'obj'], {
              key1: 'change both key1',
              key2: 'change both key2',
            })
          )
        }
      >
        change array 0 obj
      </button>
      <pre>{JSON.stringify(state, undefined, 2)}</pre>
    </div>
  );
};
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>


<div id="root"></div>

重要的部分是:

<button
  onClick={() =>
    dispatch(
      // path is an array
      setNewBuild(['array', 0, 'obj'], {
        key1: 'change both key1',
        key2: 'change both key2',
      })
    )
  }
>
  change array 0 obj
</button>

和在减速器中。

const { type } = action;
if (type === 'SET_NEW_BUILD') {
  const { path, value } = action;
  return set(state, path, () => value);
}

0
投票

要做到这一点,只需要... path 有2种方法,记住,在Redux中,所有的东西都必须是不可变的,所以不应该直接进行突变。

  • 更简单的方法。immer库 允许你进行突变操作,比如 push 或使用点运算符,但在下面是不可改变的。

  • 困难的方法:使用 价差 原生JS的对象和数组的传播操作符,但你会更好地理解事情的工作原理。

我将留下vanilla的例子,但如果你喜欢直接的东西,你可以使用immer库。

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'setNewData': {
      const { array } = state;
      const { path, value } = action.payload;

      const index = path.match(/[\d]+/g);
      const objectPath = path.split('.').slice(1);

      const [elementToReplace] = array.slice(index, index + 1);
      _.set(elementToReplace, objectPath, value); // using lodash set helper here

      const newArray = [...array.slice(0, index), elementToReplace, ...array.slice()];

      return {
        ...state,
        array: newArray,
      };
    }

    default: {
      return state;
    }
  }
}

0
投票

getState 在动作派发时给你当前的状态,另外,请注意你把动作派发成高阶函数( UI-dispatch(action(payload => dispatch => dispatch({type, payload}))


 //assuming { value } comes from UI and { path } refer to current stored data at state
 const action = value => (dispatch, getState) => 
        dispatch({type: "netNewData", value, path: getState().array[0].obj.key1}) ;


 const reducer = ( state = initialState, action) => {
  switch(action.type){
   case "setNewData":
      const { value, path } = action;
      return {...state, value, path}
   default: return state
  }
 }
© www.soinside.com 2019 - 2024. All rights reserved.