React hooks学习记录

Hook

入门及特性

1.从React 16.8开始支持得新增特性,可以在不使用类class的情况下使用state的其他React特性
2.特性:
(1)完全可选
(2)100%向后兼容
(3)现在可用

Hook概念

hook是可以在函数组件中钩入React state及生命周期等特性的函数。

  • Hook是一个函数
  • Hook有些特殊,它可以在函数组件中钩入React的特性
  • Hook它们的名字通常以use开头
    就是可以在函数组件中也可以使用state以及生命钩子函数等react特性
    :hook不能在类组件中使用

State Hook

  • useState Hook是允许在React函数组件中添加state的Hook
  • 使用场景: 在编写函数组件并需要向其添加一些state
    Hook 在 class 内部是不起作用的,但可以使用它们来取代 class 。
使用State Hook的函数组件和等价的class组件的对比
import React, { useState } from 'react'
function Example() {  
    // 声明一个叫 "count" 的 state 变量  
    const [count, setCount] = useState(0);
    return (
        <div>
            <p>You clicked {count} times</p>      
            <button onClick={() => setCount(count + 1)}> 
                Click me
            </button>    
        </div>
     )
}
class Example extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }
    render() {
        return (
            <div>
                <p>You clicked {this.state.count} times</p>
                <button onClick={() => this.setState({ count: this.state.count + 1 })}>
                    Click me
                </button>
            </div>
         )
     }
}
  • 声明State变量
    • class中通过设置this.state为count:0初始化count state为0
    • 在函数组件中直接调用State Hook来初始化count
 constructor(props) {
    super(props)
    this.state = {
        count: 0
     }
  }
  // 对比
 const [count, setCount] = useState(0)

注: useState的调用都做了些什么?
(1)定义了一个‘state变量’,变量的名称可以是任何名字
(2)useState() 方法里面需要的唯一的参数就是初始 state。不同于class可以按照需要使用任何数据类型对齐进行赋值,不一定使用对象。
(3)useState() 的返回值是当前的state变量以及更新state的函数。通过数组解构赋值的方式可以获取声明的state变量和更新函数

  • 读取State变量
// class
<p>You clicked {this.state.count} times</p>
//hook
<p>You clicked {count} times</p>
  • 更新State变量方式
    (1)class使用this.setState({})
    (2)hook使用State Hook返回的更新state变量的函数
// class
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Click me
 </button>
 // hook
  <button onClick={() => setCount(count + 1)}> 
    Click me  
  </button>
State Hook的使用

(1)从react中引入useState Hook
(2)声明一个函数组件
(3)在函数组件内部通过调用useState Hook声明了一个新的state变量。通过数组解构赋值获取useState Hook的返回值(含有当前state和更新state的函数的数组)
(4)在其他函数中使用更新state的函数通过传递形参更改state数据,React重新渲染DOM
使用useState定义state变量时候,它返回一个有两个值的数组。第一个值是当前的state,第二个值是更新state的函数

 const [fruit, setFruit] = useState('banana')
使用多个state变量
  • 如果需要使用多个state变量,可以使用useState声明多个变量
// 声明多个 state 变量
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
  • age,fruit 和 todos为当前函数组件的局部变量,并且可以单独更新
  function handleOrangeClick() {
    // 和 this.setState({ fruit: 'orange' }) 类似
    setFruit('orange');
  }

可以在函数组件中声明多个state变量,也可以使用对象和数组来声明,方便管理。为了方便于管理,可以将相关的数据归为一组
区别:class中更改state中的数据是通过setState,而hook是使用useState提供的state更新函数。setSate是更改this.state中的数据,而hook更新state变量是替换state变量而不是合并。

小结
  • hook定义:hook是可以在函数组件中钩入React state及生命周期等特性的特殊函数。

  • useState Hook可以在函数组件中声明state变量

  • useState Hook使用
    (1) 从Rect中导入useState Hook,在函数组件中存储内部state
    (2)通过useState Hook声明一个新的state变量
    useState Hook调用会返回一个包含state变量初始值更新state变量的函数,通过数组解构接收返回值
    (3)在事件中通过返回的更新函数,更新state变量
    (4)React重新渲染组件
    (5)可以使用useState Hook可以声明多个state变量
    可以将相关数据分为一组进行声明。
    注:

    • Hook 在 class 内部不起作用
      
    • state变量可以是任意数据类型,不一定是对象
      
    • 可以使用对象或数组将相关的数据存储为一组
      
    • 更新state变量是通过替换的方式,并非合并
      
import React, { useState } from 'react';
function Example() {
    const [count, setCount] = useState(0);
    return (
        <div>
             <p>You clicked {count} times</p> 
              <button onClick={() => setCount(count + 1)}>Click me</button>
        </div>
    );
 }

useEffect Hook

  • 它能在函数组件中执行副作用,并且它与 class 中的生命周期函数极为类似。
  • 数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。
    解释什么是副作用?
    首先解释纯函数(Pure function):给一个 function 相同的参数,  永远会返回相同的值,并且没有副作用;
    这个概念拿到 React 中,就是给一个 Pure component 相同的 props, 永远渲染出相同的视图,并且没有其他的副作用;
    纯组件的好处是,容易监测数据变化、容易测试、提高渲染性能等;
    副作用(Side Effect)是指一个 function 做了和本身运算返回值无关的事,比如:修改了全局变量、修改了传入的参数、甚至是 console.log(),所以 ajax 操作,修改 dom 都是算作副作用的;
    在React组件中执行的数据获取、订阅或手动修改DOM,这些操作都称为“副作用”,或简称“作用”
  • useEffect Hook作用:给函数组件增加操作副作用的能力,和class组件中的componentDidMount、componentDidUpdate和componentWillUnmount具有相同的用途
useEffect Hook使用示例
  • 使用示例
import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {    
    document.title = `You clicked ${count} times`;
   });
  return (
    <div>
        <p>You clicked {count} times</p>
        <button onClick={() => setCount(count + 1)}>
            Click me
        </button>    
     </div>
  )
}

可以把useEffect Hook比作class组件中componenDidMount、componentDidUpdate和componentWillUnmount三个周期函数的组合

React中两种常见副作用操作
无需清除的effect
  • .在 React 更新 DOM 之后运行的一些额外代码。如发送网络请求,手动变更DOM,记录日志,在执行完这些操作后就可以忽略它们
  • 对比class和hook实现这些副作用
需要清除的effect

订阅外部数据源等都需要清除,可以防止引起内存泄漏

在class和hook中无需清除的effect操作

(1)class示例

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {    
    document.title = `You clicked ${this.state.count} times`;
  } 
  componentDidUpdate() {   
    document.title = `You clicked ${this.state.count} times`; 
   }
  render() {
    return (
      <div> 
            <p>You clicked {this.state.count} times</p>        
            <button onClick={
                () => this.setState({ 
                    count: this.state.count + 1
                })
              }>         
             Click me        
            </button>     
        </div>
    );
  }}

(2)使用hook示例

import React, { useState, useEffect } from 'react';function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {   
    document.title = `You clicked ${count} times`; 
  });
  return (
    <div>   
        <p>You clicked {count} times</p>      
        <button onClick={() => setCount(count + 1)}>                    Click me     
        </button>    
    </div>
    );
 }
  • 在这个示例中useEffect做了什么?

    • 使用useEffect Hook,告诉React组件需要在渲染后执行某些操作。
    • React会保存通过useEffect传递的函数(称为“effect”),并在执行DOM更新之后调用它。
    • 在组件内部调用useEffect可以在effect中直接访问组件中的props和state,不需要特殊的API来读取它——它已经保存在函数作用域中
    • useEffect会在每次渲染后都执行,在第一次渲染之后和每次更新之后都会执行
      比较:①class组件中把副作用操作放到coponentDidMount和componentDidUpdate函数中,
      ②hook把副作用操作放到了useEffect调用的实参
在class和hook中需要清除的effect操作

(1)在class中示例
有一个 ChatAPI 模块,它允许我们订阅好友的在线状态

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this)
  }
   componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
         this.props.friend.id, 
         this.handleStatusChange
    ); 
  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
        this.props.friend.id,
        this.handleStatusChange
     );
  } 
  handleStatusChange(status) { 
    this.setState({ 
        isOnline: status.isOnline
    })
  }
  
  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
 }

在class中需要清除的effect一般会在componentDidMoun设置订阅,并在compontWillUnmount中清除

  • 使用hook与class等价的示例
import React, { useState, useEffect } from 'react'
function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null)
  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
         )*
     }
   });
    if (isOnline === null) {
        return 'Loading...';
    }
    return isOnline ? 'Online' : 'Offline';
  }
  • effect会返回一个函数,这是effect可选的清除机制。添加和移除的逻辑放在一起,effect 返回一个函数,React 将会在执行清除操作时调用它。
  • React 会在组件卸载的时候执行清除操作。React 会在执行当前 effect 之前对上一个 effect 进行清除。
  • effect和返回的函数可以为匿名
使用多个Effect实现关注点分离
  • 使用 Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。
  • Hook可以使用多个effect,可以按照用途分离副作用操作。区别于class将不相关的代码放在一起,将相关代码放在两个周期函数中的方式。
// 需要清除和不需要清除的副作用操作整合
// class
 constructor(props) {
    super(props);
    this.state = { count: 0, isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  };
  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  // hook
   const [count, setCount] = useState(0);  
   useEffect(() => {
        document.title = `You  clicked ${count} times`;
   });
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
        function handleStatusChange(status) {
            setIsOnline(status.isOnline);
        }
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
            ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
    });
  • Hook允许按照用途将代码分离,Rect会按照effect声明的顺序依次调用组件中的每一个effect。
  • Effect在每次数据更新后都会执行,在卸载时或调用effect之前都会调用的清除函数,清除上一个effect。
    优点:可以减少逻辑Bug的产生。
  • 每次渲染之前都会清除上一个effect,在运行下一个effect,保证数据的实时更新。
  • 避免了在 class 组件中因为没有处理更新逻辑而导致常见的 bug
性能优化

如果某些特定值在两次重渲染之间没有发生变化,可以避免重复渲染

  • 在 class 组件中,我们可以通过在 componentDidUpdate 中添加对 prevProps 或 prevState 的比较逻辑解决
  • hook将这个需求内置到useEffect的Hook API中
    可以添加第二个实参,通过比较第二个实参后一次的值与前一次的值,如果数据并无变化可以跳过对effect的调用,对有清除操作的effect同样适用
    注:
    • 如果调用useEffect Hook添加第二个实参,要确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。
    • 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),避免重复执行,可以传递一个空数组([])作为第二个参数。等于告诉React当前effect不依赖于props或state中的任何值,仅执行一次(有更好的方法避免,这并不是一个好方法)
// class
componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
 }
 // hook
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count])
小结
  • 副作用分为两种:需要清除的effect和不需要清除的effect
  • useEffect Hook的使用
    (1)从Rect中引入useEffect Hook
    (2)通过调用useEffect Hook传递一个函数实参(称为effect),React保存传递的函数,并且在执行DOM更新之后调用effect
    (3)在需要清除的副作用effect中可以返回一个函数,将操作添加和清除放在一起,React会在组件卸载的时候执行清除操作。
    (4)使用多个Effect实现关注点分离,可以减少逻辑Bug的产生。
      Hook可以使用多个effect,可以按照用途分离副作用操作。将不同功能的操作整合到一起。
    
    (5)可以在调用useEffect Hook添加第二个实参,即effect中使用到的state变量的数组,要保证该数组所有外部作用域中会随时间变化并且在 effect 中使用的变量
    通过比较第二个实参后一次的值与前一次的值,如果数据并无变化可以跳过对effect的调用,对有清除操作的effect同样适用,减少不必要的操作
  const [count, setCount] = useState(0);  
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  })
 //  传入第二个实参
useEffect(() => {
  document.title = `You clicked ${count} times`;
 }, [count]);

Hook 使用规则

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

  • 只能在函数最外层调用 Hook
    注:
    (1)不要在循环,条件或嵌套函数中调用 Hook
    (2)确保每次渲染中都按照同样的顺序调用,让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。
  • 只能在React的函数组件中调用Hook,或自定义的Hook中调用其他Hook
    注:
    (1)不要在其他 JavaScript 函数中调用
    目的:遵循Hook规则,确保组件的状态逻辑在代码中清晰可见,保证Rect在每一次渲染中调用Hook顺序正确
  • 扩展ESLint强制执行Hook规则的插件
    eslint-plugin-react-hooks
    详见:React Hook规则
    原因:如果将hook放到判断、循环或嵌套函数中会打乱React每次渲染Hook的顺序,不过可以将条件放到effect中

自定义Hook

作用:可以将React中提供的Hook组合到定制的Hook中,以复用不同组件之间常见的状态逻辑。
React中两种共享组件之间状态逻辑的方式:render props和高阶组件

  • 自定义Hook定义:自定义 Hook 是一个函数,名称以 “use” 开头,函数内部可以调用其他的 Hook
  • 两个函数组件之间共享逻辑,我们会把它提取到第三个可复用的函数中
import { useState, useEffect } from 'react';
// 将共享逻辑抽取到一个函数中
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
 }
 // 在函数组件中使用自定义Hook
function FriendStatus(props) {
    const isOnline = useFriendStatus(props.friend.id);
    if (isOnline === null) {
         return 'Loading...';
    }
    return isOnline ? 'Online' : 'Offline';
 }
  • 自定义Hook可以根据需要自由决定它的参数和返回值
  • 自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性。
  • 自定义Hook名称必须以‘use’开头,方便linter插件检测使用自定义Hook是否违背Hook规则
  • Hook是一种复用状态逻辑的方式,它不复用 state 本身。,每次调用自定义Hook都会创建独立的state,因此可以在同一组件中多次调用同一自定义Hook
    例子:

const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross' }
 ];

function ChatRecipientPicker() {
    const [recipientID, setRecipientID] = useState(1);
    // 将recipientID传递给自定义Hook更改在线状态
    const isRecipientOnline = useFriendStatus(recipientID);
    return (
     <div>
     <!--选择框选择选项更改recipientID值-->
        <Circle color={isRecipientOnline ? 'green' : 'red'} />
        <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
        {friendList.map(friend => (
            <option key={friend.id} value={friend.id}>{friend.name}</option>
         ))}
       </select>
    <div/>
  )
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值