React运行时——React as a UI Runtime

本篇是笔记,原文地址: 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知道你是在调用组件,它会:

  1. React 专门针对于那些渲染 UI 树并且能够响应交互的应用增强了组件特性——如果你直接调用了组件,你就只能自己来构建这些特性了。
  2. 有助于参与协调,比如React在diff时决定哪些元素更新或重建。
  3. 推迟协调,优化性能
  4. 在开发者工具中更容易调试
  5. 惰性求值——当我们在组件中使用条件渲染提前返回jsx时,那些没用到的组件就不会被执行;而调用函数的话还要执行。如下,条件成立时children所含的组件就不会被执行。
function Page({ currentUser, children }) {
  if (!currentUser.isLoggedIn) {
      return <h1>Please login</h1>;
  }
  return (
    <Layout>
      {children}
    </Layout>
  );
}

在 JSX 中<A><B /></A> 和 <A children={<B />} /> 相同。

一致性

React 将所有的工作分成了“渲染阶段”和“提交阶段”的原因。渲染阶段是当 React 调用你的组件然后进行协调的时段。在此阶段进行干涉是安全的且在未来这个阶段将会变成异步的。提交阶段 就是 React 操作宿主树的时候。而这个阶段永远是同步的。

缓存

可以通过 useMemo() Hook 获得单个表达式级别的细粒度缓存。该缓存与其相关的组件紧密联系在一起,并且将与局部状态一起被销毁。它只会保留最后一次计算的结果。

state

组件内调用 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');
 }

Context

从顶层传入的参数,所有后代组件都可以访问到,值变化时重新渲染。适用于主题等变量。

const ThemeContext = React.createContext(
  'light' // 默认值作为后备
);
function DarkApp() {
  return (
    <ThemeContext.Provider value="dark">
      <MyComponents />
    </ThemeContext.Provider>
  );
}
function SomeDeeplyNestedChild() {
  // 取决于其子组件在哪里被渲染
  const theme = useContext(ThemeContext);
  // ...
}

当 SomeDeeplyNestedChild 渲染时, useContext(ThemeContext) 会寻找树中最近的 <ThemeContext.Provider> ,并且使用它的 value 。
(事实上,React 维护了一个上下文栈当其渲染时。)
如果没有 ThemeContext.Provider 存在,useContext(ThemeContext) 调用的结果就会被调用 createContext() 时传递的默认值所取代。在上面的例子中,这个值为 ‘light’ 。

useEffect

  1. 考虑使用函数作为依赖
  2. useLayoutEffect尽量少用
  3. 使用自定义hooks

use

  1. use开头的hooks不能写在条件判断中
  2. Hooks 的内部实现其实是链表 。当你调用 useState 的时候,我们将指针移到下一项。当我们退出组件的“调用树”帧时,会缓存该结果的列表直到下次渲染开始。
// 伪代码
let hooks, i;
function useState() {
  i++;
  if (hooks[i]) {
    // 再次渲染时
    return hooks[i];
  }
  // 第一次渲染
  hooks.push(...);
}

// 准备渲染
i = -1;
hooks = fiber.hooks || [];
// 调用组件
YourComponent();
// 缓存 Hooks 的状态
fiber.hooks = hooks;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值