Hook简介

使用Hook实现加减
React Hook
v16.8.0以后的版本可以使用Hook,Kook提供了API:props,state,context,refs以及生命周期。

函数副作用:是指函数在正常工作任务之外对外部环境所施加的影响。具体地说,函数副作用是指函数被调用,完成了函数既定的计算任务,但同时因为访问了外部数据,尤其是因为对外部数据进行了写操作,从而一定程度地改变了系统环境

注意: Hook 在class 内部是不起作用的

Hook是一个特殊的函数,可以让你 钩入 React的特性。比如useState 是允许在函数组件中添加state的Hook;

import React, { useState } from 'react';

function Example() {
  // 声明一个新的叫做 “count” 的 state 变量
  const [count, setCount] = useState<number>(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Hook将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。

class给目前的工具带来了一些问题,例如class不能很好的压缩,并且会使热重载出现不稳定的情况;Hook可以让使用者在非class的情况下使用更多的React特性;

副作用: 在React组件中执行过数据获取、订阅或者手动修改过DOM;这些操作成为“副作用”或者“作用”

useEffect是一个Effect Hook,给函数组件增加了操作副作用的能力;它和class组件中的componentDidMountcomponentDidUpdatecomponentWillUnmount具有相同的用途,只不过被合并成了一个API;

Hook使用规则:

Hook就是JavaScript函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用Hook;不要再循环、条件判断或者子函数中调用;
  • 只能在React的函数组件中调用Hook;不要在其他JS函数中调用;
自定义Hook

有时候会想在组件之间重用一些状态逻辑;目前为止,有两种方案解决:高阶组件render props;自定义Hook可以在不增加组件的情况下,实现这个目标。

函数名字以“use”开头并调用其他Hook,就可以说这是一个自定义Hook。

其他Hook

useContext:不使用组件嵌套就可以订阅React的context;

一、State Hook

useState方法的返回值是当前state和更新state的函数;这与 class 里面 this.state.countthis.setState 类似。

state只在组件首次渲染时被创建;下一次重新渲染时,useState返回给我们当前的state;所以叫useState,而不叫createState

const [count, setCount] = useState()

二、Effect Hook

useEffect Hook可以看做是componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

useEffect 会在每次渲染后都执行,默认:第一次渲染后和每次更新后都会执行。

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
}

声明了count state变量,紧接着传递函数给useEffect Hook;我们把这个函数叫做 effect

componentDidMountcomponentDidUpdate 不同,使用 useEffect 调度的 effect不会阻塞浏览器更新屏幕,这会让你的应用看起来响应更快。(多数情况下,effect不需要同步执行);

不需要清除的effect: 发送网络请求,手动变更DOM,记录日志;
需要清除的effect: 订阅外部数据源;(清除的目的是防止内存泄漏)

在React class中,通常会在 componentDidMount 中设置订阅,并在componentWillUnmount 中清除它;
在useEffect中返回一个函数:会在离开组件时执行;每次渲染时,在执行当前effect之前都会对上一个effect进行清除;

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

使用Hook其中一个目的就是:解决class生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同的方法中(比如:didmount需要访问数据,willUnmount需要清除数据)

useEffect可以设置多个,React将按照effect声明的顺序依次调用组件中的每一个effect。

通过跳过Effect进行性能优化:
在react中,每次渲染后,都执行清理或者执行effect可能导致性能问题,所以可以这样解决:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。

React会等待浏览器完成画面渲染之后才会延迟调用useEffect

三、Hook规则

只在最顶层使用Hook,不在循环、条件或嵌套函数中调用Hook。

Hook的调用顺序在多次渲染之间保持一致,React就能正确将内部state和对应Hook进行关联。

如果在条件语句中调用,下一次条件为假,不执行该Hook,那么在它下面的Hook 调用都被提前执行,就会导致bug的产生。如果想要有条件地执行effect,可以将判断放在Hook的内部。

四、自定义Hook

自定义Hook就是一个普通的函数,但是名字始终以 use 开头;

如何使用自定义Hook?
如果两个函数中有重复的逻辑,我们可以将重复的逻辑抽离出来,写在一个自定义Hook函数中。

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;
}

FriendStatusFriendListItem 组件都想知道好友是否在线:

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

在两个组件中,使用相同的Hook 不会共享state
自定义Hook解决了以前在React组件中无法灵活共享逻辑的问题,在某些场景下,我们可以利用自定义Hook,如表单处理、动画、订阅声明、计时器等。

五、Hook的API索引

useState

setState 函数用于更新state,它返回的第一个值始终是更新后最新的state;
setState可以传一个数据,也可以传一个函数。

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

与class组件中的setstate 方法不同,useState 不会自动合并并更新对象。

如果初始state需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始state;注意: 此函数只在初始渲染时被调用。

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

注意: 调用State Hook的更新函数并传入当前的state时,React将跳过子组件的渲染及effect的执行。(使用object.js比较算法来比较state)

useEffect:

接收一个函数;

在函数组件主体内(这里指在React渲染阶段)改变DOM、添加订阅、设置定时器、记录日志以及包含其他副作用的操作都是不被允许的。

我们可以选择使用useEffect完成副作用操作;**一定要注意:**赋值给useEffect的函数会在组件渲染到屏幕之后执行。

理论上讲,effect只在每轮渲染结束后执行,但是可以选择让它只在某些值改变的时候才执行。这里要注意:useEffect会保证在任何新的渲染前执行。React将在组件更新前刷新上一轮渲染的effect。

(浏览器在完成布局和绘制之后,传给useEffect的函数会延迟调用,因此不能在函数中执行阻塞浏览器更新屏幕的操作)

通常来说,组件卸载时需要清除effect创建的诸如订阅或计时器ID等资源;要实现这一点,useEffect函数需要返回一个清除函数:(为防止内存泄漏,清除函数会在组件卸载前执行;同时,在执行下一个effect之前,上一个effect就已经被清除)

这意味着:下面这个函数,组件每更新一次都会创建新的订阅。

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
});

这里要注意:并非所有的effect都可以被延迟执行;例如:在浏览器执行下一次绘制前,用户可见的DOM变更必须同步执行,这样用户才不会感觉视觉上的不一致;因此React提供了useLayoutEffect Hook来处理这类effect;它和useEffect的结构相同,只是调用时机不同。

useContext

接收一个context对象(React.createcontext的返回值)并返回该context的当前值;当前的context值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop决定。

useReducer:适合管理包含多个子值的state对象
useRef

不仅仅可以拿到节点,也可以将某一个数据设置为当前ref,这样的话,新一次渲染时,该数据不会被重新渲染;

为了防止有一部分操作在组件离开时,没来得及被销毁;可以选择设置setTimeout
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值