聊聊 React hooks 及实践

混沌寄存器
• 阅读 1095

为什么 React 会提出 hooks 这种设计

越来越轻的视图层

为什么目前大多数 gui 的视图层都是越来越轻?

mvc 架构演进 mvvm 架构后带来的, mvvm 本质上就是 m -> v binder , 主要解决的问题就是 自动 updateView

在 mvc 下 需要手动 model 加载到 view 中, 然后再 updateView, 如果希望这个过程自动就会变成 m -> vm - > v

所以在这个原因下, gui 的视图层就是越来越轻的方向发展

函数式编程思想对编程语言的入侵

第三代编程语言的发展, 现在已经走向了多范式, 也都从函数式编程思想里吸取了不少, Lambda 表达式的支持就是最典型的例子

React hooks 是 React开发组对使用函数式编程思想解决视图层问题的一个实践的产物

ps: 实践: 人们能动地改造和探索现实世界一切客观物质的社会性活动

hooks 的基础用法

Hook 简介

怎么写好 hooks ?

首先要有个标准, 怎么定义好坏?

这个问题看起来很泛泛, 就像问什么样的代码写的好, 什么样的写的坏, 很难有统一的标准, 也会有个人偏好在里面, 所以在这部分只讨论些抽象的东西.
  1. 复杂度足够低, 简单
  2. 符合当前的限制性下的语境
复杂度足够低, 简单

复杂度的本质

简单解释下: 代码越短, 越容易被人理解, 就是复杂度足够低, 在使得代码变短的过程中, 我们用语法糖, 建立抽象, 封装过程, 的降低复杂度的编程手段, 在 hooks 下同样适用

符合当前的限制性下的语境

react 本身虽然只是个库, 但是说 react 代表的往往是 (react全家桶+react哲学) 对于一整套react范式编程.

范式编程 推到过程来自 架构整洁之道

  • 结构化编程是多对程序控制权的直接转移的限制。
  • 面向对象编程是对程序控制权的间接转移的限制。
  • 函数式编程是对程序中赋值操作的限制。

每个范式都约束了某种编写代码的方式,没有一个编程范式是在增加新能力 。

react 范式编程也是相同, 我们在 hooks 内程序虽然在写的时候我们没有受到编译器的限制, 但应对自己有这个意识来指导自己什么对不应该的.

当然在某种场景下, 我们一定存在不去打破限制无法实现的情况, 在这种情况下, 应该把那些看着不好的东西通过封装📦藏在 类似 utils 这类的.

最后我们回到「怎么写好 hooks ?」

我们得到下面两个结论

  1. 需要能够写好程序, 当固定好输入输出, 能够设计好一个模块, 定义好一个函数, 起好一个变量名, 都是写好程序的先决条件.
  2. 需要对 react 的机制足够了解, 对函数式编程有一定了解, 对 react 哲学有自己的感悟.

react hooks 相关机制

最好的了解方式就是造一个玩具

首先先造一个玩具, 参照 react 和 preact hooks 的实现

https://github.com/nobey/noli...

https://codesandbox.io/s/noli...

造完, 我们回来再看看 hooks

Hooks 只是数组 ?

虽然我们常说 Hooks 只是数组, 但是实际上 react 的实现其实个链表, preact 的实现倒是个数组

两个指针

hooks 在原理上其实最重要的其实不是数据, 反而是两个指针, 一个是 wipnode 当前正在工作的 vnode(fibernode) , 另一个才是 wiphook 当前 hook 指向

限制带来改变

在一个 Function 组件内部 这个写的已经不是单纯的 js , 他的运行时, 以及上下文, 已经带来了改变.

就像 正常 我们定义 let a = 1; a = 2; 的编程方式变成了 const [a, setA] = useS(1); setA(2);

然后你会发现 函数式编程是对程序中赋值操作的限制 的表现在 hooks 这部分发挥出来了,

当然这是范式上的限制, 你仍然可以 hack 出去 🐶

再聊 useCallback 和 useMemo

https://jancat.github.io/post...

使用 useCallback useMemo 要慎重, 上述这个文章是在性能方面来推断这个问题

我们换个到复杂度这个角度来看, 当一个 函数或者数据 被包裹一次之后, 这里存在两个点的复杂度的上升

  1. 包裹的方法 useCallback useMemo 我们需要对这个函数进行理解
  2. 可能存在饮用值的区别, useMemo(()=> obj, [a, b]); 如果 obj 本身是在当前 运行环境 定义的, 那么这个引用的返回就会和缓存后的不同.

性能优化不是免费的 他的成本不只是性能, 还有复杂度的上升. 除非你是指数级的计算

demo 在 diff 和 umout 是有问题的还需要更多的处理, 当前只是为了说 hooks 相关

react 奇巧淫技

让你的函数组件支持 await

demo

const sleep = () => new Promise(resolve => setTimeout(resolve, 2000))

const asyncComponent = (asyncComponent, fallback = '') => {
  let Component
  return props => {
    Component = lazy(async () => {
      const component = await asyncComponent(props);
      return { default: props => cloneElement(component, props) };
    });
    return (
      <Suspense fallback={fallback}>
        <Component {...props} />
      </Suspense>
    );
  };
};

// 我们可以异步使用组件
// 适用场景, 前置需要拉去一个或一组接口信息才显示, 这样就省掉一些模版代码

const LinkButton = asyncComponent(async props => {
  console.log({ props });
  await sleep()
  return (
    <div
      onClick={() => {
        console.log(111)
      }}
    >
      按钮
    </div>
  );
});

常规情况 lazy 是用来加载异步组件, 通过模拟 lazy 的返回的 Promise

不用 context 的全局通信

demo

/**
 * 创建跨组件跨树通讯 Hooks (可以用于的跨组件使用)
 * 思路
 * 1. 通过创建一个隐藏 React Tree 来包裹 Hook
 * 2. Hook 变化触发隐藏 React Tree 渲染
 * 3. 返回的是一个被劫持的 Hook 当 隐藏 React Tree 渲染 时会更新劫持的 Hook 数据
 * @param {Function} hook 自定义的 useHooks
 */

export const createHookObserver = (hook) => {
  const div = document.createElement("div");
  const events = new Set();
  let $data;
  const update = (data) => {
    $data = data;
    events.forEach((event) => event(data));
    return null;
  };

  render(
    createElement(() => update(hook())),
    div
  );
  const useHooks = () => {
    const [val, setVal] = useState($data);
    useEffect(() => {
      events.add(setVal);
      return () => events.delete(setVal);
    }, []);
    return val;
  };

  return useHooks;
};


const useCount = createHookObserver(() => {
  const [count, setCount] = useState(0);
  return { count, setCount };
});

最开始是 hooks 刚开始流行的时候找 hooks 通信的解决方案, 大多数都还是需要再最外部 加一个 <Provider store={store}/>

才能通行, 后来找到这个思路, 就可以 更自由的定义 任意两个组件的直接的 hooks 数据共享

一堆 hooks 状态管理

end.

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
3年前
typeScript数据类型
//布尔类型letisDone:booleanfalse;//数字类型所有数字都是浮点数numberletdecLiteral:number6;lethexLiteral:number0xf00d;letbinaryLiteral:number0b101
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Easter79 Easter79
3年前
SwiftUI 跨组件数据传递
作者:Cyandev,iOS和MacOS开发者,目前就职于字节跳动0x00前言众所周知,SwiftUI的开发模式与React、Flutter非常相似,即都是声明式UI,由数据驱动(产生)视图,视图也会与数据自动保持同步,框架层会帮你处理“绑定”的问题。在声明式UI中不存在命令式地让一个视图变成xxx