自学内容网 自学内容网

React的API✅

createContext

createContext要和useContext配合使用,可以理解为 “React自带的redux或mobx” ,事实上redux就是用context来实现的。但是一番操作下来我还是感觉,简单的context对视图的更新的细粒度把控比不上mobx,除非配合memo等优化手段来优化。(redux只用过一次不是很会👋)

以下demo介绍context的简单使用:

// store.ts
import { createContext } from "react";
export const ThemeContext = createContext({
  theme: "light",
  setTheme: (_theme: string): void => {},
});
export const AuthContext = createContext({
  name: "boyiao",
  roleType: "admin",
  setRoleType: (_roleType: string): void => {},
  setName: (_name: string): void => {},
});
// Demo.js
import { useContext, useState } from "react";
import { ThemeContext, AuthContext } from "./store";

const PageA = () => {
  console.log("Page Render");
  const { theme, setTheme } = useContext(ThemeContext);
  const { name, setName, roleType, setRoleType } = useContext(AuthContext);
  return (
    <div>
      <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
        Set Theme In Page
      </button>
      <div>{theme}</div>
      <div>{name}</div>
    </div>
  );
};

const OtherPage = () => {
  console.log("OtherPage Render");
  return (
    <div style={{ border: "1px solid red" }}>
      <div>OtherPage</div>
    </div>
  );
};

function Demo() {
  const [theme, setTheme] = useState("light");
  const [name, setName] = useState("boyiao");
  const [roleType, setRoleType] = useState("admin");
  console.log("App Render");

  return (
    <>
      <ThemeContext.Provider value={{ theme, setTheme }}>
        <AuthContext.Provider value={{ name, setName, roleType, setRoleType }}>
          <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
            Change Theme
          </button>
          <button onClick={() => setRoleType(roleType === "admin" ? "user" : "admin")}>
            Change Auth
          </button>
          <Page />
        </AuthContext.Provider>
      </ThemeContext.Provider>
      <OtherPage /> // 注意这玩意在Context之外
    </>
  );
}

export default Demo;

为什么说“简单的context对视图的更新的细粒度把控比不上mobx”?项目中常见的情况是,我们有一些需要在全局共享的状态需要管理如userName、roleType、themeToken等,如果用context来管理,为了保证这些变量可改变并触发视图重新渲染,我们不得不在根组件里用useState来保存这些全局变量。但是如果这些全局的变量改变,重新渲染影响到的范围是从最顶层往下的,成本未免太高。
见上面那个demo,OtherPage不会用到context中的变量,但是每次全局变量更新,OtherPage也要重新渲染。

但是当然有办法解决, 用memo、useMemo来缓存就好了;或者我们把需要共享的变量进行一些作用域的划分,即不要把所有需要共享的变量都生命在Root组件里面,这些都是为了降低重绘(排)的成本, 只是需要在别的地方下点功夫。只是如果用mobx则方便很多,因为mobx要求开发者用observer来显示地绑定要监听的组件:

// store.ts
import { observable, configure, action } from "mobx";
configure({ enforceActions: "always" });
interface AccountMobx {
  userName: string;
  roleType: "admin" | "user";
  setUserName: (name: string) => void;
  setRoleType: (role: "admin" | "user" ) => void;
}
const accountMobx = observable<AccountMobx>(
  {
    userName: "boyiao",
    roleType: "admin",
    setUserName(name: string) {
      this.userName = name;
    },
    setRoleType(role: "admin" | "user" ) {
      this.roleType = role;
    },
  },
  {
    setUserName: action,
    setRoleType: action
  }
);
export default accountMobx;
import { observer } from "mobx-react-lite";
import accountMobx from "./store";
const Demo = observer(() => {
  console.log("Demo render");
  return (
    <div>
      <div>{accountMobx.userName}</div>
      <button
        onClick={() => accountMobx.setUserName(accountMobx.userName + "1")}
      >
        set userName
      </button>
    </div>
  );
});
export default Demo;
import "./App.css";
import Demo from './ReactMobxDemo/index.tsx'
function App() {
  console.log("App rendered");
  return (
    <>
      <Demo />
      <div className="App">
// App Content
  </div>
    </>
  );
}
export default App;

这样的好处很直接:Demo的更新影响不到App中的其他内容。


错误边界

只用过类组件的,主要借助类组件的 componentDidCatch (他可以捕获子组件渲染过程中的错误,并在渲染组件树的过程中向上冒泡,直到它被捕获为止。)生命周期。函数组件要实现这种功能相对复杂。

import React from "react";
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError(error) {
    // 更新状态以干掉后续的渲染
    return { hasError: true };
  }
  componentDidCatch(error, errorInfo) {
    // 可以在此处记录错误信息
    console.error("错误捕获:", error, errorInfo);
  }
  render() {
    if (this.state.hasError) {
      // 自定义的降级 UI
      return <h1>出了点问题,请稍后再试。</h1>;
    }
    return this.props.children;
  }
}

伟大无需多言。


createPoral(React DOM的API)

最近发布了一个图钉组件📌到npm上面,这个组件的实现就是借助了createPoral。

portal 允许组件将它们的某些子元素渲染到 DOM 中的不同位置。这使得组件的一部分可以“逃脱”它所在的容器。例如组件可以在页面其余部分上方或外部显示模态对话框和提示框。
portal 只改变 DOM 节点的所处位置。在其他方面,渲染至 portal 的 JSX 的行为表现与作为 React 组件的子节点一致。该子节点可以访问由父节点树提供的 context 对象、事件将从子节点依循 React 树冒泡到父节点。

考虑下面这个需求:在我的React应用中有一个比待标记的节点,我希望将它标记出来:即在页面的空白处打上一个「图钉」,并将「图钉」与这个「待标记的节点」用线连接(怎么画线先不管,在我的组件里我用div来做,这么简单怎么来)。
然后我希望实现的代码逻辑如下:

const HomePage = () => {
  return (
    <div id="container">
      <div id="targetDOM">这是你想要连接的元素</div>
      <Thumbtack
        popupContainerId="container"
        targetElementId="targetDOM"
      ></Thumbtack>
    </div>
  );
};

问题来了:Thumbtack要如何封装,才能让他里面的某些DOM节点跑到#container里面,和#targetDOM产生交互?这就是createPoral的作用。

const Thumbtack = ({
popupContainerId,
targetElementId
}: { 
popupContainerId: string,
targetElementId: string
}) => {
const [popupContainer, setPopupContainer] = useState(null);
useEffect(() => {
const popupRef = document.getElementById(popupContainerId);
popupRef && setPopupContainer(popupRef);
}, [])
return 
(<div id='thumbtack'>
{ popupContainer && createPortal(<div>📌</div>, popupContainer) }
</div>)
}

最基本的思路就如上面这样简单,把📌挂载到和#targetDOM的同一个父节点下面,方便后续的绝对定位、transform等操作。但是createPoral改变的只是DOM元素渲染的位置,在上面那个Thumbtack中,< div>📌< /div>可以使用在Thumbtack里面定义的变量,而且他的事件冒泡也是冒泡到#thumbtack那里,而非#container那里。


forwardRef

forwardRef最常见的还是配合useImperativeHandle一块用,但单独使用也可以把自组件的某些DOM节点暴露给父组件。我都懒得写了直接看人家React官网吧。


keep-alive在react里如何实现?

  • 现有的方案:react-activation
  • 推荐关注的:React18 的 Offscreen
  • 自己实现:保留关键数据(如滚动的距离、需要记录的关键状态……)

原文地址:https://blog.csdn.net/YUYUYU972/article/details/143916914

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!