首页新闻动态正文

Talking about hooks【黑马web前端】

更新时间:2019年07月26日 10时53分14秒 来源:黑马程序员论坛



   从React16.8开始,Hooks API正式被React支持,而就在最近,Vue作者尤雨溪翻译并发布了一篇自己的文章《Vue Function-based API RFC》,并在全文开头强调这是Vue 3.0最重要的RFC,并在文中提到
   Function-based API 受 React Hooks 的启发,提供了一个全新的逻辑复用方案。
   可以简单的理解为,React 和 Vue 为了解决相同的问题,基于不同的技术实现了相似的API。所以本文也将结合两种框架各自的特点,简单讲讲个人对Hooks的理解。
   在未来版本的规划里,React并不如Vue激进,React的文档里专门提到
   并没有从 React 中移除 class的计划。
而Vue却采取了不同的升级策略,做好了抛弃大部分历史语法的准备
  • 兼容版本:同时支持新 API 和 2.x 的所有选项;
  • 标准版本:只支持新 API 和部分 2.x 选项。
  为什么我们不再需要Class Component?为了回答这个问题,我们先看看之前和现在的React组件划分产生了哪些变化。
  1. 既然本来就有函数组件,开始为什么引入class组件?早期的React组件可以依据“有没有状态(state)”分为
[JavaScript] 纯文本查看 复制代码
// 无状态组件
const Welcome = (props) => <h1>Hello, {props.name}</h1>;

// 有状态组件
class Welcome extends React.Component {
    constructor(props) {
        super(props);
        this.state = {name: 'KuaiGou'};
    }
    
    render() {
        return <h1>Hello, {this.state.name}</h1>;
    }
}
虽然class也可以不添加状态,但想要使一个函数组件具有状态,不得不将其转换成class组件。
直观来看,好像造成这种差异是因为在class里,我们能通过this保存和访问“状态(state)”,而函数组件在其作用域内难以维持“状态(state)”,因为再次函数运行会重置其作用域内部变量,这种差异导致了我们“不得不”使用class至今。
看来如何解决函数组件保存state的成了移除class这种“难以理解”的关键。
2. 那Hook是如何保留组件状态的?这就是我看见Hook API产生的第一个疑问。其实在React里,这并不是问题,熟悉React Fiber的同学应该知道,事实上state是保存到Fiber上的属性memoizedState上的,而并不算是class的this.state上。那状态问题就迎刃而解了,如果函数组件同样访问Fiber上的memoizedState属性,就可以解决这个问题。
基于Fiber架构,解决这个问题非常容易,将memoizedState看作一个普通的变量,那么Hook的原理就容易理解和实现了。
在文章[译] 理解 React Hooks中提到
记住,在 Hooks 的实现中也没有什么“魔术”。就像 Jamie 指出的那样,它像极了这个:
[JavaScript] 纯文本查看 复制代码
let hooks = null;

export function useHook() {
    hooks.push(hookData);
}

function reactsInternalRenderAComponentMethod(component) {
    hooks = [];
    component();
    let hooksForThisComponent = hooks;
    hooks = null
}
如Fiber一样,React实际上使用链表代替了数组这种数据结构,依次执行Hook,有兴趣的同学可以去看下React源码。
可是,class目前也能良好的支撑业务迭代,到底有什么动力去重新学习Hooks?
3. 为什么我们需要Hooks?针对这个问题,React文档提到了下面三点:
  • 在组件之间复用状态逻辑很难
  • 复杂组件变得难以理解
  • 难以理解的 class
其实我觉得第三点就是来凑数的,毕竟React推出至今一直用着class,再难用各位也都会了,会者不难难者不会嘛(反正对于刚入前端坑那时候的我来说,没有啥是容易的)。
那就回答下一个问题,目前基于class实现的生命周期函数,是否真的会造成逻辑难以复用?
答案是NO
无论高阶组件或是render props,都提供了很好的方式来达到聚合业务逻辑的目的,业务逻辑并不会被生命周期“分割”。
那到底是哪里引入了复杂度?熟悉套娃的同学...呸
熟悉Ajax、Promise的等异步API的同学可能还记得“回调地狱”。类似的,高阶函数、render props等也极容易造成“嵌套地狱”,结合装饰器、函数式的compose等嵌套起来才是真的爽...一直嵌套一直爽...
但是,无论是什么地狱肯定是不好的,那一起来看最后一个问题。
复杂组件变得难以理解
之所以回避前两个问题,是因为我个人认为,无论是class还是HOC,它们都很好的解决了它们需要解决的问题,虽然生命周期函数将很多业务逻辑拆分的七零八碎,但是HOC却依旧能把它们集合在一起,仅考虑保留生命周期而言,就像Function-based一样(这是后话)。
所以我们换一个思路不难发现,真正的问题是在于它们在抽象业务逻辑的时候貌似引入了不必要的概念,才使得逻辑复用困难和难以理解。
这些概念导致了过多的嵌套,加深了组件层级,层级之间互相牵扯,就像我现在兜里的耳机线一样。
Hook独特之处在于化繁为简。
真正繁琐的是层级与层级之间的关系,我将借用React文档关于自定义Hook的例子说明这个问题
[JavaScript] 纯文本查看 复制代码
import React, { useState, useEffect } from 'react';
// 通过friendID订阅好友状态
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
通过useFriendStatus这个自定义的hook,我可以非常轻松的在下面两个组件中实现逻辑的复用
[JavaScript] 纯文本查看 复制代码
// FriendStatus获取好友状态
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

// FriendListItem获取好友状态
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
};
可是对于熟悉高阶组件的同学来说(不熟悉的同学请看高阶组件)依旧可以轻松的提取一个名叫useFriendStatus的高阶组件
[JavaScript] 纯文本查看 复制代码
function useFriendStatus(WrappedComponent) {
  return class extends React.Component {
    componentDidMount() {
        ChatAPI.subscribeToFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
    }
     
    componentDidUpdate(prevProps) {
        ChatAPI.unsubscribeFromFriendStatus(
          prevProps.friend.id,
          this.handleStatusChange
        );
        ChatAPI.subscribeToFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
      }

    componentWillUnmount() {
        ChatAPI.unsubscribeFromFriendStatus(
          this.props.friend.id,
          this.handleStatusChange
        );
    }
    
    render() {
      return <WrappedComponent isOnline={isOnline} />;
    }
  }
}

然后,分别套上FriendStatus和FriendListItem依旧可以非常完美的多处复用此逻辑。
需要注意的是,高阶组件等概念还是有很多特点和优点的,hooks并不是万能的,hooks只是hooks而已
需要提到的是,官方文档在这里对比了新旧方案代码量的长度和复杂度,但其实在特定的业务情况下,确实不可避免会出现这种问题,个人认为这是次要矛盾(既然HOC已经封装了复杂度,还纠结里面长不长、复不复杂干啥)。
反而,这容易让刚接触Hooks的同学忽略Hook最大的亮点,先看下面这段熟悉的代码(不熟悉的同学看Promise 对象和async 函数)。
[JavaScript] 纯文本查看 复制代码
// async 
const P1 = new Promise(Something)
const P2 = new Promise(Something)

export default async function () {
  const res1 = await P1;
  //do Something
  const res2 = await P2;
  // ...
  return res2
}
和刚才的Hook对比
[JavaScript] 纯文本查看 复制代码
// 以及 hook
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);
  ...
  return isOnline ? 'Online' : 'Offline';
}
像不像...就问你像不像...(模仿团队某成员说话)
写(粘)了这么多代码,简单来说,Hook解决的就是“嵌套地狱”的问题,正如async解决“回调地狱”一样。它们都做到了将原来不同“维度”的代码封装到了同一维度,以达到更直观、透明的将“计算结果”传递下去的目的。
而class不得不借助高阶组件等等概念,解决代码复用等问题,但是由于引入额外的概念(函数)反而使得代码更加复杂,现在的class难以解决这个问题,所以他就被抛弃了。
问题来了,谁可以不引入别的概念,完成逻辑封装?
就是函数本身啊!!还要class干嘛?!
综上,被抛弃和class生命周期函数导致的代码复杂度提升无关,Hook简化生命周期函数只是不过是举手之劳,并不是什么重要的特性。

原帖:https://juejin.im/post/5d0ae589518825122925c2de

推荐了解热门学科

java培训 Python人工智能 Web前端培训 PHP培训
区块链培训 影视制作培训 C++培训 产品经理培训
UI设计培训 新媒体培训 产品经理培训 Linux运维
大数据培训 智能机器人软件开发




传智播客是一家致力于培养高素质软件开发人才的科技公司“黑马程序员”是传智播客旗下高端IT教育品牌。自“黑马程序员”成立以来,教学研发团队一直致力于打造精品课程资源,不断在产、学、研3个层面创新自己的执教理念与教学方针,并集中“黑马程序员”的优势力量,针对性地出版了计算机系列教材50多册,制作教学视频数+套,发表各类技术文章数百篇。

传智播客从未停止思考

传智播客副总裁毕向东在2019IT培训行业变革大会提到,“传智播客意识到企业的用人需求已经从初级程序员升级到中高级程序员,具备多领域、多行业项目经验的人才成为企业用人的首选。”

中级程序员和初级程序员的差别在哪里?
项目经验。毕向东表示,“中级程序员和初级程序员最大的差别在于中级程序员比初级程序员多了三四年的工作经验,从而多出了更多的项目经验。“为此,传智播客研究院引进曾在知名IT企业如阿里、IBM就职的高级技术专家,集中研发面向中高级程序员的课程,用以满足企业用人需求,尽快补全IT行业所需的人才缺口。

何为中高级程序员课程?

传智播客进行了定义。中高级程序员课程,是在当前主流的初级程序员课程的基础上,增加多领域多行业的含金量项目,从技术的广度和深度上进行拓展“我们希望用5年的时间,打造上百个高含金量的项目,覆盖主流的32个行业。”传智播客课程研发总监于洋表示。




黑马程序员热门视频教程【点击播放】

Python入门教程完整版(懂中文就能学会) 零起点打开Java世界的大门
C++| 匠心之作 从0到1入门学编程 PHP|零基础入门开发者编程核心技术
Web前端入门教程_Web前端html+css+JavaScript 软件测试入门到精通


在线咨询 我要报名
和我们在线交谈!