React
简介
React 是一个用于构建Web和原生交互界面的库,核心概念是组件化设计和声明式编程。
React 的特点:当数据发生变化时,UI 能够自动把变化反映出来。
虚拟 DOM
虚拟 DOM(Virtual DOM),是对真实 DOM 的一个轻量级表示,保存在内存中。通过 Object 模拟真实的 DOM 节点对象,再通过特定的 render 方法将其渲染成真实的 DOM 节点。
在每次组件更新时,React 会先生成新的虚拟 DOM 树,并与之前的虚拟 DOM 树进行 diff,只对比出变化的部分,再应用到真实 DOM 上,从而避免了大量的 DOM 操作。
以前是基于浏览器 DOM 的 API 去控制 DOM 节点的创建、修改和删除。
虚拟 DOM 的优点:
- 跨平台
- 声明式编程
- 处理兼容性:对浏览器的原生事件进行封装,提供统一的事件处理方式
Diff 算法
Diff 算法是 Virtual DOM 的核心算法,它用来比较两个 Virtual DOM 树的差异,找出需要更新的最小集合,然后批量更新到真实的 DOM 上。
通过给每个节点设置一个唯一的 key,Diff 算法可以快速找到相同的节点,减少比较次数。
最小化更新
找出差异后,react 会计算出需要更新的最小操作集,只更新实际 DOM 中变化的部分,而不是重绘整个页面。
批量更新
Virtual DOM 可以将多次状态变更合并成一次更新,而不是每次状态变化就立即更新 DOM,减少了对 DOM 的操作次数,提高了性能。
优先级调度
react 使用优先级调度机制,确保高优先级的更新优先执行。
比如「用户输入」比「网络请求后的渲染」优先级更高,会优先执行。
Fiber 架构
错误边界
错误边界(Error Boundary)是一个 React 组件,该组件可以捕获其子组件的错误,并渲染出备用 UI。
默认情况下,如果 React 渲染期间发生错误,整个组件树都会被卸载。例如有一个父组件,有若干个子组件,当其中一个子组件报错时,整个组件树都不会被渲染。理想情况是,其他组件正常渲染,而报错的组件被隐藏或者替换成备用 UI。
可以使用 react-error-boundary 解决这个问题。
pnpm add react-error-boundary
示例,因为子组件 Foo 报错,导致父组件的内容也无法渲染,页面空白。
import { useState } from 'react';
function Foo() {
// 这里会报错,没有bar方法
bar();
return <div>foo</div>;
}
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return (
<>
<div>{count}</div>
<button onClick={handleClick}>click</button>
<Foo />
</>
);
}
export default App;
使用react-error-boundary
后,控制台依然会报错,但是页面正常渲染,错误组件会渲染出备用 UI。
import { useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
function Foo() {
bar();
return <div>foo</div>;
}
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return (
<>
<div>{count}</div>
<button onClick={handleClick}>click</button>
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<Foo />
</ErrorBoundary>
</>
);
}
export default App;
fallback 就是备用 UI,当子组件报错时,会渲染出这个备用 UI。
结合 TS 使用
React.FC
不推荐写 React.FC
type Props = {
name?: string,
age?: number
};
// 不推荐
const App: React.FC<Props> = props => {};
// 推荐
const App = (props: Props) => {};
通过 vite 创建 react 项目
通过 vite 创建的 react 项目里,
ReactDOM.createRoot(document.getElementById('root')!)
,这里最后的!
的作用是什么?
!
的作用是为了告诉 TS 编译器:document.getElementById('root')
不会是 null
,不要对此产生类型错误警告。
在 TypeScript 中,!
后缀运算符被称为非空断言操作符。当应用于表达式时,它告诉编译器:「我确信这个表达式的值在这个上下文中不会是 null
或 undefined
」。
在 ReactDOM.createRoot(document.getElementById('root')!)
这行代码中,document.getElementById('root')
返回的是 HTMLElement | null
类型,因为 DOM API 可能找不到与给定 ID 匹配的元素,此时会返回 null
。然而在实际应用中,通常有一个 id 为 root
的 HTML 元素用于挂载 React 应用,所以「断言」这里不可能是 null
。
加上 !
后,ts 编译器会忽略对这一表达式可能为 null
或 undefined
的检查,并假设它始终是一个非空的 HTMLElement
类型实例,这样就可以安全地调用 .createRoot()
方法。