likes
comments
collection
share

面试在即,重温下redux的实现原理

作者站长头像
站长
· 阅读数 24

1. 前言

在这个充满不确定性的时代,裁员和失业潮似乎成了职场人挥之不去的阴影。

卷学历,卷大厂经历,卷经验好像是各个前端群里每天的谈资了~~~

想象一下,在面试官一脸严肃地询问:“你对状态管理有什么了解?”时,你自信满满地说:“哦,Redux,这个我挺熟的!” 或许,这种已经get的技能会提高我们的自信心,让你在面试官的心里印象分疯狂拔高吧。

面试在即,重温下redux的实现原理

2. 页面展示效果

面试在即,重温下redux的实现原理

3. 使用方法

让我们回顾下怎么使用的,从使用方法上慢慢切入到源码里~~~

业务组件

import store from "./store/index";

store.subscribe(() => {
  console.log("logging...", store.getState());
});

function App() {

  return (
    <div className="App">
      <header className="App-header">
        <div>
          <button
            onClick={() => {
              store.dispatch({ type: "INCREMENT_COUNT" });
            }}
          >
            +++
          </button>
        </div>
      </header>
    </div>
  );
}

export default App;


store.js

import { combineReducers, createStore } from "./redux";

let initState = {
  counter: { count: 0 },
};

function counterReducer(state, action) {
  switch (action.type) {
    case "INCREMENT_COUNT":
      return { count: state.count + 1 };
    case "DECREMENT_COUNT":
      return { count: state.count - 1 };
    default:
      return state;
  }
}

const reducers = combineReducers({
  counter: counterReducer,
});

const store = createStore(reducers, initState);

export default store;


从上面的代码可以看出

业务组件中

  • 使用了store.dispatch();进行派发。

  • 使用store.subscribe进行模拟监听派发更改state数据的结果。

store.js中

  • 暴露了两个核心函数combineReducers, createStore,最终导出createStore的返回值供各个业务组件使用。

  • combineReducers接收reducer,接收的目的可能是函数里对reducer会进行逻辑开发

  • createStore接收两个参数,一个是combineReducer函数的返回值,一个是初始化state

这样来看的话,我们的目的是要搞明白这两个核心函数的实现就可以了。

4. combineReducers实现

小伙伴们先看下源码


export const combineReducers = function (reducers) {
  // reducers = {count: setCount}
  const keys = Object.keys(reducers); // ['count']
  return function (state = {}, action) {
    const nextState = {};
    keys.forEach((key) => {
      const reducer = reducers[key];
      const prev = state[key]; // { count: 0 }
      const next = reducer(prev, action); // 0 + 1 || 0 - 1
      nextState[key] = next;
    });
    return nextState;
  };
};


通过上面的代码,我们可以发现这几点:

  • Object.keys提取接收对象的key,返回一个只有key的数组(下面用keys代替)

  • 函数返回出去一个新函数,这个新函数会成为createStore的形参(新函数会在createStore函数中的dispatch被触发时执行)

因为这个新函数可以理解为是一个回调,目前还没有执行,所以我们还不知道函数的两个形参分别代表啥?

这里我直接剧透~~~

reducer(initState, action)

  • 第一个参数:initState是初始state(和createStore脱不开关系)

  • 第二个参数:action是dispatch时传的action

  • 函数内逻辑:

    • 遍历keys

    • 得到key对应的counterReducer函数

    • 得到key对应的初始state值{ count: 0 }

    • 得到action

    • 执行counterReducer({ count: 0 }, action)

    • 如果接收action是'INCREMENT_COUNT'执行{ count: state.count + 1 };

    • 返回结果{ count: 1 }

combineReducers函数的实现到目前就告一段落了

核心其实就是 接收reducer集合返回出去一个闭包函数(函数在dispatch时执行,这个函数的返回值是action和reducer的结晶)

5. createStore实现

老样子,先看下代码


export const createStore = function (reducer, initState) {
  let listeners = [];
  let state = initState;

  function subscribe(listener) {
    // 我们希望,订阅了数据的handler,在数据改变时,都能执行。
    listeners.push(listener);
  }

  function dispatch(action) {
    // 单向数据流,而不是双向绑定。
    const newState = reducer(state, action); // 触发combineReducers的闭包
    state = newState;
    listeners.forEach((fn) => fn());
  }

  dispatch({ type: Symbol() });

  // 如果别人想要获取数据?
  function getState() {
    return state;
  }

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

到现在,我们看createStore的实现方式应该就很清晰了~~~

函数接收两个参数

  • 第一个是combineReducers返回的函数

  • 第二个是初始化state

定义一个subscribe函数

在业务组件中被调用时,把业务组件传过来的回调函数压到listeners栈里

定义一个dispatch函数

  • 接收action

  • 执行reducer(state, action),把变更后的state值存到state中

  • 执行listeners栈

dispatch({ type: Symbol() });

初始化store的state, 初始触发一次 reducer 的执行,从而确保 state 的初始值被正确设置并通知所有订阅者

定义一个getState函数

方便在业务组件中获取最新的state

createStore函数的实现到目前就告一段落了

核心是在dispatch时,通过接收到的actioncombineReducers返回的闭包函数进行reducer的触发,更新state

到这,本篇文章的核心redux的基本实现原理就结束啦~~~

休息一下~~~

面试在即,重温下redux的实现原理

6. 优化页面取值

细心的小伙伴会发现,我们目前在业务组件中取值用的还是通过订阅,更改state后,执行回调得到最新的state值。

效果如下

面试在即,重温下redux的实现原理

我如果想更优雅,更符合项目中的写法怎么做呢?

我们可以通过react-reduxuseSelector钩子

通过create-react-app脚手架安装的项目,根目录会有个index.js

我们在里面引入Provider全局包裹下,让所有子组件共享store

index.js

import { Provider } from "react-redux";
import store from "./store";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

想使用useSelector钩子,根index.js必须要Provider全局包裹,别忘了

业务组件中

import store from "./store/index";
import { useSelector } from "react-redux";

store.subscribe(() => {
  console.log("logging...", store.getState());
});

function App() {
  const count = useSelector((state) => state.counter.count);
  return (
    <div className="App">
      <header className="App-header">
        <div>数字:{count}</div>
        <div>
          <button
            onClick={() => {
              store.dispatch({ type: "INCREMENT_COUNT" });
            }}
          >
            +++
          </button>
        </div>
      </header>
    </div>
  );
}

export default App;


7. redux, react-redux, @reduxjs/toolkit

它们的大概描述如下:

  • Redux是一个包,用来集中管理项目的全局状态

  • React-Redux 是一个React绑定库,用于将React组件与Redux的store连接起来

  • @reduxjs/toolkit 是一个工具包,旨在简化Redux的使用,减少样板代码,并提供最佳实践

8. 总结

到这,今天分享的redux笔记就结束啦~~~

通过理解和熟练掌握这些概念,你将为学习和使用React打下坚实的基础。

不知道坚持看下来的小伙伴有多少? 希望坚持看到这的小伙伴给博主点个赞支持一下吧~~~

欢迎转载,但请注明来源。 最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。

面试在即,重温下redux的实现原理

转载自:https://juejin.cn/post/7392501157774245940
评论
请登录