一、React.lazy 懒加载
我们可以使用 Suspense 组件来渲染 lazy 组件,这样就可以在等待加载 lazy 组件时做优雅降级(例:loading 指示器等)。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
同时除了组件以外,这种加载方法还能运用到路由上
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
不过这里要注意的是,React.lazy 目前暂时只能支持默认导出(default exports),如果你想要以命名导出的话(name exports),你可以创建个中间模块。例:
// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));
二、Context
一般 React 应用中,我们遵循都是自上而下的数据流。但有些情景下,有些数据是多个子级,或者说孙级组件中需要使用到,这样传递是极其繁琐的,Context 就是用来解决这个问题的,它提供了一种可以在组件之间共享值。
简例
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
以上的例子向我们展示了:
- 创建 Context 对象:React.createContext(defaultValue)
- 建立 Provider 组件:每个 Context 对象都会返回一个 Provider 组件,接收一个 value 属性,如果没有 value 的话,就会使用 Context 的 defaultValue
- 取值:Provider 允许多层嵌套,里层的数据会覆盖外层的数据
- 更新:只要 value 数值改变,它内部所有使用到的组件都会重新渲染,不受 shouldComponentUpdate 函数影响
如果你想要在任何生命周期,包括 render 中访问到创建的 contect 对象呢,只要把它挂载到 class 的 contextType 上就可以。
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
MyClass.contextType = MyContext;
当然,我们也可以用 hook 来使用 context
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
三、错误边界
有时候我们不想因为 UI?的 javascript 导致整个应用崩溃,当抛出错误的时候,我们可以在 class 组件通过定义 static getDerivedStateFromError() 渲染备用 UI ,通过 componentDIdCatch() 来打印错误信息。
简例
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
然后,你就可以把他当做一个常规组件直接去使用
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
错误边界无法捕获以下场景错误
- 事件处理,可以用 try / catch
- 异步代码
- 服务端渲染
- 错误边界组建自身的错误(并非来自它的子组件)
四、Fragments
react 一个常见的情况就是组件返回多个顶级元素,但为了规则,还是必须在根节点再套一层,导致了不必要的嵌套,而 Fragment 就是用来改善这种情况的
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
// 当然你也可以使用短语法,相同效果
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
五、Profiler
profiler 是用来测量一个 react 应用多久渲染一次,以及渲染一次的代价。
简例
render(
<App>
<Profiler id="Panel" onRender={callback}>
<Panel {...props}>
<Profiler id="Content" onRender={callback}>
<Content {...props} />
</Profiler>
<Profiler id="PreviewPane" onRender={callback}>
<PreviewPane {...props} />
</Profiler>
</Panel>
</Profiler>
</App>
);
function onRenderCallback(
id, // 发生提交的 Profiler 树的 “id”
phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
actualDuration, // 本次更新 committed 花费的渲染时间
baseDuration, // 估计不使用 memoization 的情况下渲染整棵子树需要的时间
startTime, // 本次更新中 React 开始渲染的时间
commitTime, // 本次更新中 React committed 的时间
interactions // 属于本次更新的 interactions 的集合
) {
// 合计或记录渲染时间。。。
}
不过切记这个组件不要用在线上环境!
六、Diffing 算法
为了减少频繁对 dom 树的操作,react 实现了 diff 算法,通过根组件来绘制 dom 树。
- 如果根节点元素更改,根节点以下所有东西都会重载,状态销毁。
- 如果相同元素,那只更改不同的属性
- 如果更改子元素,请添加列表上独一无二的 key 值给它,确保它在无更新的情况下不会被更新
|