本篇是笔记,原文地址: https://overreacted.io/zh-hans/react-as-a-ui-runtime/
在本篇文章中,我会从最佳原则的角度尽可能地阐述 React 编程模型。我不会解释如何使用它 — 而是讲解它的原理。
React 元素并不是永远存在的 。它们总是在重建和删除之间不断循环着。
React 元素具有不可变性。例如,你不能改变 React 元素中的子元素或者属性。如果你想要在稍后渲染一些不同的东西,你需要从头创建新的 React 元素树来描述它。
条件渲染中不需要渲染时写成null以占位,让react能对比元素顺序以重用。
key 给予 React 判断子元素是否真正相同的能力,即使在渲染前后它在父元素中的位置不是相同的。
React 组件中对于 props 应该是纯净的。
惰性初始化是被允许的即使它不是完全“纯净”的:
function ExpenseForm() {
// 只要不影响其他组件这是被允许的:
SuperCalculator.initializeIfNotReady();
// 继续渲染......
}
只要调用组件多次是安全的,并且不会影响其他组件的渲染,React 并不关心你的代码是否像严格的函数式编程一样百分百纯净。在 React 中,幂等性比纯净性更加重要。
也就是说,在 React 组件中不允许有用户可以直接看到的副作用。换句话说,仅调用函数式组件时不应该在屏幕上产生任何变化。
组件属于函数,因此我们可以直接进行调用
let reactElement = Form({
showMessage: true });
ReactDOM.render(reactElement, domContainer);
组件函数名称按照规定需要大写。当 JSX 转换时看见 而不是 ,它让对象 type 本身成为标识符而不是字符串:
console.log(<form />.type); // 'form' 字符串
console.log(<Form />.type); // Form 函数
但是为什么我们要编写 而不是 Form() ?因为如果是前者,React知道你是在调用组件,它会:
function Page({
currentUser, children }) {
if (!currentUser.isLoggedIn) {
return <h1>Please login</h1>;
}
return (
<Layout>
{
children}
</Layout>
);
}
React 将所有的工作分成了“渲染阶段”和“提交阶段”的原因。渲染阶段是当 React 调用你的组件然后进行协调的时段。在此阶段进行干涉是安全的且在未来这个阶段将会变成异步的。提交阶段 就是 React 操作宿主树的时候。而这个阶段永远是同步的。
可以通过 useMemo() Hook 获得单个表达式级别的细粒度缓存。该缓存与其相关的组件紧密联系在一起,并且将与局部状态一起被销毁。它只会保留最后一次计算的结果。
组件内调用 setState 并不会立即执行重渲染。相反,React 会先触发所有的事件处理器,然后再触发一次重渲染以进行所谓的批量更新。如下代码相当于三次 setCount(1) 调用
const [count, setCounter] = useState(0);
function increment() {
setCounter(count + 1);
}
function handleClick() {
increment();
increment();
increment();
}
如果希望3次都在前一次结果上累加,则可以将setState的参数写成一个函数
setCounter(c => c + 1);
如果更新逻辑更复杂,可以考虑使用useReducer Hook,action 字段可以是任意值,尽管对象是常用的选择。
const [counter, dispatch] = useReducer((state, action) => {
if (action === 'increment') {
return state + 1;
} else {
return state;
}
}, 0);
function handleClick() {
dispatch('increment');
dispatch('increment');
dispatch('increment');
}
从顶层传入的参数,所有后代组件都可以访问到,值变化时重新渲染。适用于主题等变量。
const ThemeContext = React.createContext(
'light' // 默认值作为后备
);
function DarkApp() {
return (
<ThemeContext.Provider value="dark">
<MyComponents />
</ThemeContext.Provider>
);
}
function SomeDeeplyNestedChild() {
// 取决于其子组件在哪里被渲染
const theme = useContext(ThemeContext);
// ...
}
当 SomeDeeplyNestedChild 渲染时, useContext(ThemeContext) 会寻找树中最近的
(事实上,React 维护了一个上下文栈当其渲染时。)
如果没有 ThemeContext.Provider 存在,useContext(ThemeContext) 调用的结果就会被调用 createContext() 时传递的默认值所取代。在上面的例子中,这个值为 ‘light’ 。
// 伪代码
let hooks, i;
function useState() {
i++;
if (hooks[i]) {
// 再次渲染时
return hooks[i];
}
// 第一次渲染
hooks.push(...);
}
// 准备渲染
i = -1;
hooks = fiber.hooks || [];
// 调用组件
YourComponent();
// 缓存 Hooks 的状态
fiber.hooks = hooks;