首页 > 解决方案 > Is it possible to use React Hooks outside of functional component, or i have to use mobx or redux?

问题描述

I am new to React, and when I was reading about the docs, I found there were two ways to implement React components, functional-based and class-based. I know before React 16.8 it's not possible to manage state in functional components, but after that there is React Hooks.

The problem is, there seems to be one restriction for React Hooks, they can only be used inside functional components. Take a server-client as an example, which needs to change an isAuthenticated state while 401 received.

//client.js
import { useUserDispatch, signOut } from "auth";

export function request(url, args) {
  var dispatch = useUserDispatch();
  return fetch(url, args).then(response => {
  if (response.status === 401) {
    logout(dispatch);
  }
}
);

//auth.js
import React from "react";

var UserStateContext = React.createContext();
var UserDispatchContext = React.createContext();

function userReducer(state, action) {
  ...
}

function UserProvider({ children }) {
  var [state, dispatch] = React.useReducer(userReducer, {
    isAuthenticated: false,
  });

  return (
    <UserStateContext.Provider value={state}>
      <UserDispatchContext.Provider value={dispatch}>
        {children}
      </UserDispatchContext.Provider>
    </UserStateContext.Provider>
  );
}

function useUserState() {
  return React.useContext(UserStateContext);
}

function useUserDispatch() {
  return React.useContext(UserDispatchContext);
}

function signOut(dispatch) {
  dispatch({});
}

export { UserProvider, useUserState, useUserDispatch, loginUser, signOut };

The client code above will produce error "Hooks can only be called inside of the body of a function component". So maybe I have to move line var dispatch = useUserDispatch() upward to the component where request is called, and pass dispatch as props to request. I feel this is not right, no only request is forced to care about some meaningless(to it) dispatch, but also this dispatch will spread everywhere a component needs to request.

For class-based components, this.state doesn't solve this problem either, but at least I can use mobx.

So are there some other ideal ways to solve this problem?

标签: javascriptreactjsreact-hooksmobxmobx-react

解决方案


Mobx 和 hooks 在实现上非常相似。两者都使用某种意义上的“全局”渲染上下文。React 将渲染上下文与组件渲染上下文联系起来,但 Mobx 将渲染上下文分开。因此,这意味着必须在组件渲染生命周期内创建挂钩(但有时可以在该上下文之外调用)。Mobx-react 将 Mobx 渲染生命周期与反应生命周期联系起来,当观察到的对象发生变化时触发反应重新渲染。所以 Mobx-react 将 React 渲染上下文嵌套在 Mobx 渲染上下文中。

React 在内部跟踪 hooks 的次数和在组件渲染周期内调用 hook 的顺序。另一方面,Mobx 使用代理包装任何“可观察”对象,让 Mobx 上下文知道在 Mobx“运行上下文”(autorun本质上是调用)期间是否引用了它的任何属性。然后,当一个属性发生变化时,Mobx 知道什么“运行上下文”关心该属性,并重新运行这些上下文。这意味着您可以在任何可以访问可观察对象的地方更改其属性,而 Mobx 会对其做出反应。

对于 react 状态钩子,react 为状态对象提供了一个自定义的 setter 函数。然后 React 使用对该 setter 的调用来了解何时需要重新渲染组件。该setter可以在任何地方使用,甚至在 React 渲染之外,但您只能在渲染调用中创建该钩子,因为否则 react 无法告诉该钩子要绑定到哪个组件。创建一个钩子隐式地将它连接到当前渲染上下文,这就是为什么必须在渲染调用中创建钩子的原因:钩子构建器在渲染调用之外没有任何意义,因为它们无法知道它们连接到什么组件——但是一旦绑定到一个组件,那么它们就需要在任何地方都可用。事实上,onClickfetch回调不会发生在渲染上下文中,尽管回调通常在该上下文中创建的 - 动作回调发生在反应完成渲染之后(因为 javascript 是单线程的,所以渲染函数必须在其他任何事情发生之前完成)。


推荐阅读