返回

React 中根据日期选择动态更新 Redux dispatch 值

javascript

React 中如何根据用户选择的日期更新 dispatch 函数的值?

最近遇到个挺有意思的问题,需要在用户选择 renewOn 日期时,更新 dispatch 函数中的值。折腾了半天,试了各种方法,最后发现问题比想象中复杂一点,但也找到了解决的思路,跟大家分享下。

一、 问题是怎么出现的?

通常,我们使用 dispatch 来触发 Redux 中的 action,更新应用状态。 问题出在,dispatch 函数通常在组件渲染时就确定了,如果想根据用户后续的交互(比如选择日期)来动态改变 dispatch 中 action 的值,就有点麻烦了。 假设初始代码长这样:

import React, { useState } from 'react';
import { useDispatch } from 'react-redux';

function MyComponent() {
  const dispatch = useDispatch();
  const [renewOn, setRenewOn] = useState('');

  const handleDateChange = (event) => {
    setRenewOn(event.target.value);
    // 这里想根据 renewOn 的值更新 dispatch
    // dispatch({ type: 'UPDATE_RENEW_DATE', payload: renewOn }); // 这样写不行!
  };

  return (
    <div>
      <input type="date" value={renewOn} onChange={handleDateChange} />
      {/* ... 其他内容 ... */}
    </div>
  );
}

直接在 handleDateChangedispatchpayload 用的还是旧的 renewOn 值。因为 setRenewOn 是异步的,dispatch 执行的时候,renewOn 还没更新呢!

二、 咋解决这个问题呢?

遇到这种问题,直接用 useState 肯定是不行的,因为状态更新是异步的,几次尝试后,我整理了几种可行的办法,分享如下:

1. 利用 useEffect

useEffect 可以监听状态变化,并在状态更新后执行副作用。

import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';

function MyComponent() {
  const dispatch = useDispatch();
  const [renewOn, setRenewOn] = useState('');
  const [tempRenewOn,setTempRenewOn] = useState('')

  const handleDateChange = (event) => {
     setTempRenewOn(event.target.value)
  };
    useEffect(()=>{
        if(tempRenewOn){
            setRenewOn(tempRenewOn);
            dispatch({ type: 'UPDATE_RENEW_DATE', payload: tempRenewOn });
        }
    },[tempRenewOn])

  return (
    <div>
      <input type="date" value={renewOn} onChange={handleDateChange} />
      {/* ... 其他内容 ... */}
    </div>
  );
}

原理:

  • handleDateChange 里,先把用户选择的新日期存到tempRenewOn里。
  • useEffect 监听 tempRenewOn,一旦它变了,就执行里面的函数。
  • 如果tempRenewOn不为空,那么就将tempRenewOn的值赋给renewOn,执行dispatch,将 action 派发出去。
  • dispatch action 时,renewOn 已经是最新的值了。

2. 使用 useRef

useRef 可以创建一个可变的引用,它的值在组件的整个生命周期内保持不变,并且可以直接修改。

import React, { useState, useRef } from 'react';
import { useDispatch } from 'react-redux';

function MyComponent() {
  const dispatch = useDispatch();
  const [renewOn, setRenewOn] = useState('');
  const renewOnRef = useRef('');

  const handleDateChange = (event) => {
    setRenewOn(event.target.value);
    renewOnRef.current = event.target.value;
    dispatch({ type: 'UPDATE_RENEW_DATE', payload: renewOnRef.current });
  };

  return (
    <div>
      <input type="date" value={renewOn} onChange={handleDateChange} />
      {/* ... 其他内容 ... */}
    </div>
  );
}

原理:

  • renewOnRef 保存 renewOn 的最新值。
  • handleDateChange 中,同步更新 renewOnRef.current
  • dispatch action 时,直接用 renewOnRef.current,保证是最新的值。

额外说明:

useRef 除了存储值,还可以用来引用 DOM 元素。它比 useState 更“轻量”,因为它不会触发组件重新渲染。

3. 在 Action Creator 中处理

把日期选择的逻辑放到 Action Creator 里,Action Creator 返回一个函数,这个函数接收 dispatchgetState 作为参数。

// actions.js
export const updateRenewDate = (newDate) => (dispatch, getState) => {
    // 如果需要,可以从 getState() 获取当前状态
    // const currentState = getState();

    dispatch({ type: 'UPDATE_RENEW_DATE', payload: newDate });
};

// MyComponent.js
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { updateRenewDate } from './actions';

function MyComponent() {
  const dispatch = useDispatch();
  const [renewOn, setRenewOn] = useState('');

  const handleDateChange = (event) => {
    setRenewOn(event.target.value);
    dispatch(updateRenewDate(event.target.value));
  };

  return (
    <div>
      <input type="date" value={renewOn} onChange={handleDateChange} />
      {/* ... 其他内容 ... */}
    </div>
  );
}

原理:

  • Action Creator updateRenewDate 接收新的日期值。
  • 返回的函数内部,可以访问 dispatch,直接派发 action,payload 是最新的日期值。
  • 这种写法对异步逻辑有更好扩展, 更方便做复杂的逻辑处理,比如发起网络请求。

4. 使用自定义 Hook

可以把逻辑封装成一个自定义 Hook,让代码更简洁。

// useRenewDate.js
import { useState, useRef } from 'react';
import { useDispatch } from 'react-redux';

function useRenewDate() {
  const dispatch = useDispatch();
  const [renewOn, setRenewOn] = useState('');
  const renewOnRef = useRef('');

  const handleDateChange = (event) => {
    setRenewOn(event.target.value);
    renewOnRef.current = event.target.value;
    dispatch({ type: 'UPDATE_RENEW_DATE', payload: renewOnRef.current });
  };

  return { renewOn, handleDateChange };
}

export default useRenewDate;

// MyComponent.js
import React from 'react';
import useRenewDate from './useRenewDate';

function MyComponent() {
  const { renewOn, handleDateChange } = useRenewDate();

  return (
    <div>
      <input type="date" value={renewOn} onChange={handleDateChange} />
      {/* ... 其他内容 ... */}
    </div>
  );
}

原理:
其实就是把前面的逻辑封装了一下,组件里代码看起来清爽多了。

安全提示:

对于日期选择,一般要做好输入校验,防止用户输入无效的日期。可以借助一些日期库(比如 date-fnsmoment)来处理日期的格式化、校验等操作。

三. 进阶技巧: 结合 Redux-Thunk/Redux-Saga

如果涉及到异步操作,比如选择日期后要向服务器发送请求,更新数据库里的数据,就需要 Redux-Thunk 或 Redux-Saga 了。

Redux-Thunk 示例:

// actions.js
export const updateRenewDate = (newDate) => (dispatch) => {
  dispatch({ type: 'UPDATE_RENEW_DATE_REQUEST' }); // 发送请求开始的 action

  // 模拟异步请求
  setTimeout(() => {
    dispatch({ type: 'UPDATE_RENEW_DATE_SUCCESS', payload: newDate }); // 请求成功的 action
  }, 1000);
};

// MyComponent.js (与前面类似,略)

Redux-Saga 示例:

// sagas.js
import { call, put, takeLatest } from 'redux-saga/effects';

function* updateRenewDateSaga(action) {
  try {
    // 模拟异步请求
    yield call(delay, 1000); // delay 是一个返回 Promise 的函数
    yield put({ type: 'UPDATE_RENEW_DATE_SUCCESS', payload: action.payload }); // 请求成功的 action
  } catch (error) {
    yield put({ type: 'UPDATE_RENEW_DATE_FAILURE', error }); // 请求失败的 action
  }
}

export function* watchUpdateRenewDate() {
  yield takeLatest('UPDATE_RENEW_DATE_REQUEST', updateRenewDateSaga);
}

// actions.js
export const updateRenewDate = (newDate) => ({
  type: 'UPDATE_RENEW_DATE_REQUEST',
  payload: newDate,
});
// MyComponent.js,以及store的配置需要做对应更改.

小结一下

以上几种方案各有优劣:

  • useEffect 比较直观,适合简单的场景。
  • useRef 更灵活,性能更好,适合需要手动管理状态的情况。
  • Action Creator 方案把逻辑和 UI 分离,代码更清晰,方便测试。
  • 自定义 Hook 进一步封装,提高代码复用性。
  • 涉及异步请求,那就老老实实用 Redux-Thunk 或 Redux-Saga。

具体选择哪种方案,根据项目情况决定就行, 其实也可以相互结合使用。希望对大家有帮助。