likes
comments
collection
share

React-Redux 与 Context 之个人浅见

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

引子

最近面试被问到了react-redux跟Context之间的区别,答:

它们两者都解决了跨层级组件间数据共享的问题; react-redux 比较适合在 大型项目中使用, context 比较适合在小型项目中使用;

面试官:...

通过面试,我发现确实有些问题想的不够深入,听别人一说,觉得有道理,然后认为就是这样了,没有深究其深层次原因;或者工作中遇到了,直接觉得这个方案不行,立马换一个方案,没有及时记录;

发现了问题,就来解决它,于是我查了一些资料,整理了一下,谈一下当下自己的理解,后面会随着技术广度提升后,再来更新修饰,如果有大佬有更好的解释,也希望能够在评论区留下宝贵见解;

历史背景

状态提升

通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去

总体就是一个组件内的State会影响其他组件的State,那么我们就应该将它提升到这些组件的父组件中去统一管理;

具体可以查看React官网的例子

后面遇到了再补充一个例子,现实中这种场景应该还是不少的。

属性跨层传递;

前面说了状态提升的场景,这种方式在某些场景下会导致一定的问题的,比如下图:

React-Redux 与 Context 之个人浅见

比如ComponentA 与 ComponentB 之间需要沟通,那么就需要将数据与方法提升到SubRoot中;如果ComponentB出现了某个问题,我们要往上查找多层,如果用的js, 并且属性传递的时候是用拓展运算符(好处是后面底层组件中添加从顶层组件获取的某个属性,可以不动中间组件,坏处是如果底层组件看它的某个属性哪来的不是很方便)传递下去,那么维护这段代码的人观感可能不太好(如果不是他写的😀)。

还有一种情况是单纯的顶层组件保存着数据,某个底层组件需要使用该数据,如下图App 组件中有用户信息,Message组件使用用户信息(显示欢迎语);

React-Redux 与 Context 之个人浅见

这种方式容易污染中间组件,使中间组件的复用困难,因为它不干净了,承担了数据传递的职责。

问题总结;
  1. 状态方法都提升到父级组件,容易使父级组件逻辑臃肿;
  2. 一个组件更新,会导致父组件更新,进而导致所有组件更新(虽然可以通过一些方式打断,但是这些中间组件本可以不做);
  3. 组件重用困难,处于中间层的组件,有了一些它并不需要的属性;

Context 与 React-Redux的 使用方式

知道了问题,看下react-redux 和 Context 的解决方式:

它们都可以跨组件直传属性;分别看一下它们的使用方式:

Context使用

为了方便,写在了一个文件里:

const UserContext = createContext();
export const App = () => {
  const [user, setUser] = useState({});

  useEffect(() => {
    setTimeout(() => {
      setUser({ username: "Bob", userId: 0 });
    }, 500);
  }, []);

  console.log("app render");
  return (
    <div>
      <UserContext.Provider value={{ user, setUser }}>
        {useMemo(
          () => (
            <>
              <NavBar />
              <MainPage />
            </>
          ),
          []
        )}
      </UserContext.Provider>
    </div>
  );
};

const NavBar = () => {
  const { setUser } = useContext(UserContext);
  console.log("nav-bar:render");
  return (
    <div>
      this is NavBar!!{" "}
      <button
        onClick={() => {
          setUser((user) => ({ ...user, username: "Tom" }));
        }}
      >
        修改用户名
      </button>
    </div>
  );
};

const MainPage = () => {
  console.log("main-page:render");
  return (
    <div>
      <Content />
    </div>
  );
};

const Content = () => {
  console.log("content:render");

  return (
    <div>
      this is Content
      <div>
        <Message />
      </div>
    </div>
  );
};

const Message = () => {
  console.log("message:render");
  const { user } = useContext(UserContext);

  return <div>hello {user.username}</div>;
};
react-redux的使用

使用 react-redux(也是为了方便写在一个文件里,实际开发还是得分开):下面是最原始的写法,需要定义较多变量和模板代码(现在redux 官方推荐使用Redux Toolkit,本例未使用)

// service
const getUser = (params: unknown) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        username: "Tom",
        userId: 0,
      });
    }, 500);
  });
};

// action type
type UserAction = Action<ValueOf<typeof USER_MODIFY_ACTION>> & {
  payload: any;
};

const USER_MODIFY_ACTION = {
  SET_USER: "setUser",
  SET_NAME: "setName",
} as const;

// reducer 
const userReducer: Reducer<User, UserAction> = (
  // @ts-ignore
  state = { username: "", userId: undefined },
  action
) => {
  // 这里可以使用对象来优化switch case;
  switch (action.type) {
    case USER_MODIFY_ACTION.SET_USER:
      return action.payload as User;
    case USER_MODIFY_ACTION.SET_NAME:
      return { ...state, username: action.payload as string };
    default:
      return state;
  }
};

// actions 
const getUserAction = (params: unknown) => {
  return (dispatch) =>
    getUser(params).then((userInfo) => {
      dispatch({
        type: USER_MODIFY_ACTION.SET_USER,
        payload: userInfo,
      });
    });
};
const updateUserNameAction = (username: string) => {
  return {
    type: USER_MODIFY_ACTION.SET_NAME,
    payload: username,
  };
};

// createStore
const reducers = combineReducers({ user: userReducer });
const enhancers = applyMiddleware(thunk);
const store = createStore(reducers, enhancers);

// 组件部分
export const App = () => {
  return (
    <Provider store={store}>
      <InnerApp />
    </Provider>
  );
};

const InnerApp = () => {
  const dispatch = useDispatch<ThunkDispatch<State, never, UserAction>>();

  useEffect(() => {
    dispatch(getUserAction({}));
  }, []);
  
  console.log("app render");

  return (
    <div>
      <NavBar />
      <MainPage />
    </div>
  );
};

const NavBar = () => {
  const dispatch = useDispatch();
  console.log("nav-bar:render");
  return (
    <div>
      this is NavBar!!{" "}
      <button
        onClick={() => {
          dispatch(updateUserNameAction("Bob"));
        }}
      >
        修改用户名
      </button>
    </div>
  );
};

const MainPage = () => {
  console.log("main-page:render");
  return (
    <div>
      <Content />
    </div>
  );
};

const Content = () => {
  console.log("content:render");

  return (
    <div>
      this is Content
      <div>
        <Message />
      </div>
    </div>
  );
};

const Message = () => {
  const user = useSelector<State, State["user"]>((state) => state.user);
  console.log("message:render");

  return <div>hello {user.username}</div>;
};

使用对比

现在可以看一下这两个例子从以下几个维度,试着回答开篇的问题:

程序性能

我们观察控制台的打印,发现redux在数据更新的时候渲染的组件更少,并且Context 组件需要额外的优化,总的来说,redux 性能更好,context更适合用静态数据的场景,例如主题信息,语言信息;

左侧是使用context的,右侧是使用redux的;

React-Redux 与 Context 之个人浅见 React-Redux 与 Context 之个人浅见

职责单一

Context Provider 顶层组件用户交互的逻辑与数据管理逻辑耦合在了一起;

注意context的数据是存在对应的组件上的,该组件销毁后,也就一同销毁了,但是redux是全局独立于组件树的,需要自己手动销毁(如果不销毁,大型应用里可能会导致内存爆炸),它的数据存储独立于组件外,代码组织上更灵活;

可重用性

context 有隐式的状态依赖,例如如果Message脱离了提供者组件的包裹,程序会抛出错误; redux 严格说也有这个问题,但是项目中一般在顶层使用,出现这个问题的几率较小;

这个地方只是一个点,其实这两者都有解决办法;context 用 Consumer;redux 用connect;来包裹复用组件即可;

可维护性

调试工具: react-redux 提供了redux dev tools; context 基于react-dev-tool, redux dev tools 更详细些;

总结

redux底层也是用的context来实现的;它们有各自的应用场景,如果是静态的全局数据共享场景或者数据量小,可以使用它,引入简单;如果是一个页面有很多组件,组件间交互多且复杂,那么react-redux是不错的一个选择,因为它从性能和代码可维护性都比context要好些;如果数据层级跟组件层级是对应的,那么使用props分发下去就可以(个人认为)

贴一个chatGpt 给出的答案:

React-Redux 与 Context 之个人浅见

参考链接

【react】context VS redux

What is redux and what problem does it solve?

# The Problem with React's Context API

# When to Use Context API vs Redux

# A better way of solving prop drilling in React apps

# Idiomatic Redux: The History and Implementation of React-Redux

# React 实践进阶指南