专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

react hooks如何初步替代redux

看到标题,你会想到什么???有什么解决方案

  • useReduce + useContext
  • react-redux 提供的 useSelector , useDispatch

我们来通过class版本往react hook版本一步步递进
我们来通过使用复选框点击事件来看结果

react hook 替换 redux 解决方案

class版本

import { ToggleState } from '../store/reducers/toggle';
import { RootState } from '../store/reducers';

type Props = ToggleState & typeof action;
class Toggle extends React.Component<Props, any> {
  render(){
    const { ui, toggleSwitch } = this.props;
    return (
      <div>
        <div>{JSON.stringify(ui)}</div>
        <input
          type="checkbox"
          value={ui.toggle ? 'true': 'false'}
          onChange={(e)=>toggleSwitch(e)}
        />
      </div>
    );
  }
}

const mapStateToProps = (state: RootState):ToggleState => state.toggle;

export default connect(mapStateToProps, action)(Toggle);

67_1.png我们用class版本先把功能做出来
触发完一次支持sum值加1, toggle根据当前checked的状态设置

函数组件版本:中间版本1

import action from '../store/actions/toggle'
import { ToggleState } from '../store/reducers/toggle';
import { RootState } from '../store/reducers';

type Props = typeof action;
const Toggle2 = ({toggleSwitch}:Props) => {
  const { ui }: ToggleState = useSelector((state: RootState):ToggleState => state.toggle);
  return (
    <div>
      <div>{JSON.stringify(ui)}</div>
      <input
        type="checkbox"
        value={ui.toggle ? 'true': 'false'}
        onChange={(e)=>toggleSwitch(e)}
      />
    </div>
  );
}

export default connect(null, action)(Toggle2);

我们将class组件替换成了函数组件,其他的不变
还不是我们想要的结果,继续优化咯~~~

react-redux hook useSelector useDispatch

  • 使用 useSelector hook,我们可以读取我们的状态
  • useDispatch hook 让我们执行 redux 操作
type Props = typeof action;
// 使用 useSelector hook,我们可以读取我们的状态。
// useDispatch hook 让我们执行 redux 操作
// 我们可以在任何事件监听器中调用 dispatch 函数。
const Toggle3 = () => {
  const { ui }: ToggleState = useSelector((state: RootState):ToggleState => state.toggle);
  const dispatch = useDispatch();
  return (
    <div>
      <div>{JSON.stringify(ui)}</div>
      <input
        type="checkbox"
        value={ui.toggle ? 'true': 'false'}
        onChange={(e: ChangeEvent<HTMLInputElement>)=>dispatch({type: 'toggleSwitch', value: e.currentTarget.checked})}
      />
    </div>
  );
}
export default Toggle3;

当然这边我有其他class组件例子使用了redux,我们希望整改是一点点改掉,所以肯定会出现一部分使用react hook函数组件, 一部分使用class组件

let reducers: ReducersMapObject<ReducerState, AnyAction> = {
  counter1,
  counter2,
  toggle
}

export type RootState = {
  [P in keyof typeof reducers]: ReturnType<typeof reducers[P]>
}

使用 useSelector 获取到对应的store状态使用, useDispatch 帮助我们获取到store的dispatch方法,帮助我们调用对应的reducer

useContext + useReducer版本

// ====== ToggleReducer组件 ======
import { ContextParam, AppContext } from './test';
const ToggleReducer = () => {
  const { state, dispatch } = useContext<ContextParam>(AppContext);
  const { ui } = state;
  return (
    <div>
      <div>{JSON.stringify(ui)}</div>
      <input
        type="checkbox"
        value={ui.toggle ? 'true': 'false'}
        onChange={(e: ChangeEvent<HTMLInputElement>)=>dispatch!({type: 'toggleSwitch', value: e.currentTarget.checked})}
      />
    </div>
  );
}
export default ToggleReducer;

// ====== ToggleReducer1组件 ======
import { ContextParam, AppContext } from './test';
const ToggleReducer1 = () => {
  const { state, dispatch } = useContext<ContextParam>(AppContext);
  const { ui } = state;
  return (
    <div>
      <div>{JSON.stringify(ui)}</div>
      <input
        type="checkbox"
        value={ui.toggle ? 'true': 'false'}
        onChange={(e: ChangeEvent<HTMLInputElement>)=>dispatch!({type: 'toggleSwitch', value: e.currentTarget.checked})}
      />
    </div>
  );
}
export default ToggleReducer1;

// ====== Test组件 ======
export interface ContextParam { 
  state?: any,
  dispatch?: Dispatch<AnyAction>
}
export const AppContext: Context<ContextParam> = React.createContext({});
const Test = () => {
  const [state, dispatch] = useReducer(reducers, {
    ui:{
      toggle: false,
      sum: 0
    }
  });
  return (
    <AppContext.Provider value={{state, dispatch}}>
      <ToggleReducer />
      <ToggleReducer1 />
    </AppContext.Provider>
  );
}

使用上useContext,useReducer 混合之后,效果可见:

67_2.png有人要说了,不能直接通过useReducer就实现redux功能么?
你可以试试,试完之后可以发现,不使用Context 上下文和useContext去帮助你,可以了解下react-redux提供的功能就知道了,还真做不到

redux中间件hooks化

说到react hook替换到老式的redux了,如果到后期了,所有的都得改掉,那么中间件没提供方案怎么办,我们可以手写个,也很简单的呀~

useLogger

我们使用 useLogger 模拟redux-logger中间件功能

type Reducer<S, A> = (prevState: S, action: A) => S;
/**
 * useLogger 模拟redux-logger中间件
 * @param reducer 
 * @param initState 
 */
function useLogger(reducer: Reducer<StateParam, any>, initState: StateParam):[StateParam, any] {
  const [state, dispath] = useReducer(reducer, initState);
  function loggerDispatch(action:any) {
    console.log('修改之前状态:', state);
    dispath(action);
  }
  useEffect(() => { 
    console.log('修改之后状态:', state);
  }, [state])
  return [state, loggerDispatch];
}

调用方式

function App() {
  const initState: StateParam = { number: 0 };
  const [state, dispath] = useLogger(reducer, initState)
  return (
    <div>
      <div>number: {state.number}</div>
      <button onClick={() => dispath({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispath({ type: 'DECREMENT' })}>-</button>
    </div>
  )
}

中间件的操作基本上是封装原有的store.dispatch

67_3.png

useThunk

我们也想异步加载中间件,我们这边我就用useThunk来模拟redux-thunk中间件

/**
 * useThunk 模拟redux-thunk中间件
 * thunk的action调用接收2个参数 , 第一个是dispatch,第二个是getState
 * @param reducer 
 * @param initState 
 */
function useThunk(reducer: Reducer<StateParam, any>, initState: StateParam):[StateParam, any] {
  const [state, dispath] = useLogger(reducer, initState);
  function thunkDispatch(action: any) {
    if (action && typeof action === 'function') {
      return action(thunkDispatch, () => state);
    }
    return dispath(action);
  }
  return [state, thunkDispatch];
}

调用方式

function App() {
  const initState: StateParam = { number: 0 };
  const [state, dispath] = useThunk(reducer, initState)
  return (
    <div>
      <div>number: {state.number}</div>
      <button onClick={() => dispath({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispath({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispath(() => { 
        setTimeout(() => {
          dispath({ type: 'INCREMENT' })
        }, 1000);
      })}>thunkplus</button>
    </div>
  )
}

usePromise

我们再来用usePromise模拟下redux-promise

function usePromise(reducer: Reducer<StateParam, any>, initState: StateParam):[StateParam, any] {
  const [state, dispath] = useLogger(reducer, initState);
  function promiseDispatch(action: any) {
    if (action && typeof action.then === 'function') {
      action.then(promiseDispatch)
    }
    dispath(action);
  }
  return [state, promiseDispatch];
}

调用方式

function App() {
  const initState: StateParam = { number: 0 };
  const [state, dispath] = usePromise(reducer, initState)
  return (
    <div>
      <div>number: {state.number}</div>
      <button onClick={() => dispath({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispath({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispath(new Promise(resolve => { 
          setTimeout(() => {
            resolve({ type: 'INCREMENT' })
          }, 1000);
        })
      )}>promiseplus</button>
    </div>
  )
}

我们挑了几个常见简单的中间件入手实践了一波,简单demo已经调研,可以使用到项目中去了,大家有什么见解可以发评论区大家一起讨论哈!感谢你的阅读,比心~~~~

文章永久链接:https://tech.souyunku.com/42697

未经允许不得转载:搜云库技术团队 » react hooks如何初步替代redux

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们