Vue3中使用hooks,hooks究竟是个啥?如何理解

Posted 治愈云_q1942951600

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue3中使用hooks,hooks究竟是个啥?如何理解相关的知识,希望对你有一定的参考价值。

1.前言

最近被hooks这个词老是被提出,我经常听到但是又不会用,于是乎抽时间认真学习一下。

2.vue3 hooks

2.1 为什么vue3 可以使用hooks

因为vue2由选项式Api转为组合式Api。底层源码改了好多。
组合式API的好处;
就是在函数内可以使用声明周期。

2.2使用hooks的好处与优点

无论是 vue 还是 react, 通过 Hooks 写法都能做到,将“分散在各种声明周期里的代码块”,通过 Hooks 的方式将相关的内容聚合到一起。
这句话让我豁然开朗。
因为以前vue2 选项式api把各个生命周期的东西分散开来,在写小项目的时候肯定是比较规范的。但是当项目大的时候,很多相似的业务确因为声明周期放在不同的模块里面。
使用了函数式API就我没有这个问题了。

将相同的功能卸载一起。并且每一个代码块里面可以使用声明周期函数。代码复用性直线提升。

3.hooks的起源

3.1区别函数式组件与类组件

最开始是react的东西;react之前的写法都是类组件。然后函数式组件是不能有状态的。不能调用声明周期函数。

//1.创建函数式组件
		function MyComponent()
			console.log(this); 
    //此处的this是undefined,因为babel编译后开启了严格模式,严格模式最大的特点就是禁止自定义的函数中this指向window
			return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
		
		//2.渲染组件到页面,如果第一个参数直接传入函数名会报函数类型不能作为React的节点错误,必须以标签的形式传入而且如果名字是小写的话会报当前这个标准在浏览器中不被允许,因为如果是小写的标签会被转成html标签,但是html中并没有此标签,还要注意标签必须要闭合
		ReactDOM.render(<MyComponent/>,document.getElementById('test'))

函数式组件

//1.创建类式组件,1、必须要继承React.Component,2、必须要有render函数,3、render函数中必须要返回值 
		class MyComponent extends React.Component 
			render()
				//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。但是有个问题就是我们并没有手动去new MyComponent这个实例,但是下面的ReactDOM.render函数中写了组件标签后React会自动的帮我们创建实例
				
				//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
				console.log('render中的this:',this);
				return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
			
		
		//2.渲染组件到页面
		ReactDOM.render(<MyComponent/>,document.getElementById('test'))

类组件

3.2 useState 与useEffect

useState: 函数式组件里面的通过这个钩子函数调用state(等同与vue2里面的data,就是挂载的数据)。完成状态改变的操作

import React,  useState  from 'react';
 
function CounterWithHooks(props) 
	const [count, setCount] = useState(props.initialValue);
 
	return (
		<div>
			<h2>This is a counter using hooks</h2>
			<h1>count</h1>
			<button onClick=() => setCount(count + 1)>Click to Increment</button>
		</div>
	);

 
export default CounterWithHooks;

useEffect 声明周期了 相当于声明周期钩子,不太懂;对react了解少。

import React,  Component  from 'react';
 
class App extends Component 
	componentDidMount() 
		console.log('I have just mounted!');
	
 
	render() 
		return <div>Insert JSX here</div>;
	


function App() 
	useEffect(() => 
		console.log('I have just mounted!');
	);
 
	return <div>Insert JSX here</div>;

4.vue 3中的组合式函数

从vue3 官网上面取例子。在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。
鼠标跟踪器示例

<script setup>
import  ref, onMounted, onUnmounted  from 'vue'

const x = ref(0)
const y = ref(0)

function update(event) 
  x.value = event.pageX
  y.value = event.pageY


onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>

<template>Mouse position is at:  x ,  y </template>

但是,如果我们想在多个组件中复用这个相同的逻辑呢?我们可以把这个逻辑以一个组合式函数的形式提取到外部文件中:

// mouse.js
import  ref, onMounted, onUnmounted  from 'vue'

// 按照惯例,组合式函数名以“use”开头
export function useMouse() 
  // 被组合式函数封装和管理的状态
  const x = ref(0)
  const y = ref(0)

  // 组合式函数可以随时更改其状态。
  function update(event) 
    x.value = event.pageX
    y.value = event.pageY
  

  // 一个组合式函数也可以挂靠在所属组件的生命周期上
  // 来启动和卸载副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // 通过返回值暴露所管理的状态
  return  x, y 


在组件中的使用

<script setup>
import  useMouse  from './mouse.js'

const  x, y  = useMouse()
</script>

<template>Mouse position is at:  x ,  y </template>

5.开源项目中vue3 中hook的应用;

开源项目地址 https://github.com/fect-org/fect

5.1 vue3 使用useState

该作者在vue3 中使用hooks的实践

import  ref, readonly  from 'vue'
import type  UnwrapRef, Ref, DeepReadonly  from 'vue'

export type SetStateAction<S> = S | ((prevState: S) => S)

export type Dispatch<T> = (val: SetStateAction<T>) => void

const useState = <T>(initial?: T) => 
  const state = ref<T | undefined>(initial)
  const dispatch = (next: SetStateAction<T>) => 
    const draft = typeof next === 'function' ? (next as (prevState: T) => T)(state.value as T) : next
    state.value = draft as UnwrapRef<T>
  
  return [readonly(state), dispatch] as [DeepReadonly<Ref<T>>, Dispatch<T>]


export  useState 

具体使用

import  useEventListener, useState  from '@fect-ui/vue-hooks'
  //===============
  setup(props,  slots, emit ) 
    const affixRef = ref<HTMLElement>()

    const [fixed, setFixed] = useState<boolean>(false)
    const [style, setStyle] = useState<AffixLayout>( width: 0, height: 0 )

    const offset = computed(() => convertUnitToPx(props.offset))

    const scrollHandler = () => 
      if (!affixRef.value || isHidden(affixRef)) return
      const  position  = props
      const affixRect = getDomRect(affixRef)
      const scrollTop = getScrollTop(window)
      setStyle(
        width: affixRect.width,
        height: affixRect.height
      )
      if (position === 'top') 
        setFixed(() => offset.value > affixRect.top)
      
      if (position === 'bottom') 
        const  clientHeight  = document.documentElement
        setFixed(() => clientHeight - offset.value < affixRect.bottom)
      

5.2思考我们应用如何应用
当项目小的时候我们大概率是用不上了 。比如一个简单的登录和注册。大概率是应用不上了。

import  reactive, toRefs  from 'vue'
import  login, register  from '@/service/user'
import  setLocal  from '@/common/js/utils'
import  Toast  from 'vant'

export default 
  setup() 
    
    const state = reactive(
      username: '',
      password: '',
      email1: '',
      username1: '',
      password1: '',
      type: 'login',
      verify: ''
    )

    // 切换登录和注册两种模式
    const toggle = (v) => 
      state.type = v
      state.verify = ''
    
  
    // 提交登录或注册表单
    const onSubmit = async (values) => 
   
      if (state.type == 'login') 
        const  data  = await login(
          "email": values.username,
          "password": values.password
        )
        console.log("token",data)
        setLocal('token', data.content)
        
        // 需要刷新页面,否则 axios.js 文件里的 token 不会被重置
        window.location.href = '/'
        
       else 
        await register(
          "username": values.username1,
          "password": values.password1,
          "email":values.email1
        )
        Toast.success('注册成功')
        state.type = 'login'
        state.verify = ''
      
    
    return 
      ...toRefs(state),
      toggle,
      onSubmit,
  
    
  


</script>

React Hooks究竟是什么呢?

摘要: React Hooks原理解析。

我们大部分 React 类组件可以保存状态,而函数组件不能? 并且类组件具有生命周期,而函数组件却不能?

React 早期版本,类组件可以通过继承PureComponent来优化一些不必要的渲染,相对于函数组件,React 官网没有提供对应的方法来缓存函数组件以减少一些不必要的渲染,直接 16.6 出来的 React.memo函数。

React 16.8 新出来的Hook可以让React 函数组件具有状态,并提供类似 componentDidMountcomponentDidUpdate等生命周期方法。

类被会替代吗?

Hooks不会替换类,它们只是一个你可以使用的新工具。React 团队表示他们没有计划在React中弃用类,所以如果你想继续使用它们,可以继续用。

我能体会那种总有新东西要学的感觉有多痛苦,不会就感觉咱们总是落后一样。Hooks 可以当作一个很好的新特性来使用。当然没有必要用 Hook 来重构原来的代码, React团队也建议不要这样做。

Go Go

来看看Hooks的例子,咱们先从最熟悉的开始:函数组件。

以下 OneTimeButton 是函数组件,所做的事情就是当我们点击的时候调用 sayHi 方法。

    import React from 'react';
    import  render  from 'react-dom';
    
    function OneTimeButton(props) 
      return (
        <button onClick=props.onClick>
            点我点我
        </button>
      )
    
    
    function sayHi() 
      console.log('yo')
    
    
    render(
      <OneTimeButton onClick=sayHi/>,
      document.querySelector('#root')
    )

我们想让这个组件做的是,跟踪它是否被点击,如果被点击了,禁用按钮,就像一次性开关一样。

但它需要一个state,因为是一个函数,它不可能有状态(React 16.8之前),所以需要重构成类。

函数组件转换为类组件的过程中大概有5个阶段:

  • 否认:也许它不需要是一个类,我们可以把 state 放到其它地方。
  • 实现: 废话,必须把它变成一个class,不是吗?
  • 接受:好吧,我会改的。
  • 努力加班重写:首先 写 class Thing extends React.Component,然后 实现 render等等 。
  • 最后:添加state。

    class OneTimeButton extends React.Component 
      state = 
        clicked: false
      
    
      handleClick = () => 
        this.props.onClick();
    
        // Ok, no more clicking.
        this.setState( clicked: true );
      
    
      render() 
        return (
          <button
            onClick=this.handleClick
            disabled=this.state.clicked
          >
            You Can Only Click Me Once
          </button>
        );
      
    

这是相当多的代码,组件的结构也发生了很大的变化, 我们需要多个小的功能,就需要改写很多。

使用 Hook 轻松添加 State

接下来,使用新的 useState hook向普通函数组件添加状态:

    import React,  useState  from 'react'
    
    function OneTimeButton(props) 
      const [clicked, setClicked] = useState(false)
      
      function doClick() 
        props.onClick();
        setClicked(true)
      
    
      return (
        <button
          onClick=clicked ? undefined : doClick
          disabled=clicked
        >
          点我点我
        </button>
      )
    

这段代码是如何工作的

这段代码的大部分看起来像我们一分钟前写的普通函数组件,除了useState

useState是一个hook。 它的名字以“use”开头(这是Hooks的规则之一 - 它们的名字必须以“use”开头)。

useState hook 的参数是 state 的初始值,返回一个包含两个元素的数组:当前state和一个用于更改state 的函数。

类组件有一个大的state对象,一个函数this.setState一次改变整个state对象。

函数组件根本没有状态,但useState hook允许我们在需要时添加很小的状态块。 因此,如果只需要一个布尔值,我们就可以创建一些状态来保存它。

由于Hook以某种特殊方式创建这些状态,并且在函数组件内也没有像setState函数来更改状态,因此 Hook 需要一个函数来更新每个状态。 所以 useState 返回是一对对应关系:一个值,一个更新该值函数。 当然,值可以是任何东西 - 任何JS类型 - 数字,布尔值,对象,数组等。

现在,你应该有很多疑问,如:

  • 当组件重新渲染时,每次都不会重新创建新的状态吗? React如何知道旧状态是什么?
  • 为什么hook 名称必须以“use”开头? 这看起来很可疑。
  • 如果这是一个命名规则,那是否意味着我可以自定义 Hook。
  • 如何存储更复杂的状态,很多场景不单单只有一个状态值这么简单。

Hooks 的魔力

将有状态信息存储在看似无状态的函数组件中,这是一个奇怪的悖论。这是第一个关于钩子的问题,咱们必须弄清楚它们是如何工作的。

原作者得的第一个猜测是某种编译器的在背后操众。搜索代码useWhatever并以某种方式用有状态逻辑替换它。

然后再听说了调用顺序规则(它们每次必须以相同的顺序调用),这让我更加困惑。这就是它的工作原理。

React第一次渲染函数组件时,它同时会创建一个对象与之共存,该对象是该组件实例的定制对象,而不是全局对象。只要组件存在于DOM中,这个组件的对象就会一直存在。

使用该对象,React可以跟踪属于组件的各种元数据位。

请记住,React组件甚至函数组件都从未进行过自渲染。它们不直接返回HTML。组件依赖于React在适当的时候调用它们,它们返回的对象结构React可以转换为DOM节点。

React有能力在调用每个组件之前做一些设置,这就是它设置这个状态的时候。

其中做的一件事设置 Hooks 数组。 它开始是空的, 每次调用一个hook时,React 都会向该数组添加该 hook

为什么顺序很重要

假设咱们有以下这个组件:

    function AudioPlayer() 
      const [volume, setVolume] = useState(80);
      const [position, setPosition] = useState(0);
      const [isPlaying, setPlaying] = useState(false);
    
      .....
    

因为它调用useState 3次,React 会在第一次渲染时将这三个 hook 放入 Hooks 数组中。

下次渲染时,同样的3hooks以相同的顺序被调用,所以React可以查看它的数组,并发现已经在位置0有一个useState hook ,所以React不会创建一个新状态,而是返回现有状态。

这就是React能够在多个函数调用中创建和维护状态的方式,即使变量本身每次都超出作用域。

多个useState 调用示例

让咱们更详细地看看这是如何实现的,第一次渲染:

  1. React 创建组件时,它还没有调用函数。React 创建元数据对象和Hooks的空数组。假设这个对象有一个名为nextHook的属性,它被放到索引为0的位置上,运行的第一个hook将占用位置0

  2. React 调用你的组件(这意味着它知道存储hooks的元数据对象)。

  3. 调用useState,React创建一个新的状态,将它放在hooks数组的第0位,并返回[volume,setVolume]对,并将volume 设置为其初始值80,它还将nextHook索引递增1。
  4. 再次调用useState,React查看数组的第1位,看到它是空的,并创建一个新的状态。 然后它将nextHook索引递增为2,并返回[position,setPosition]
  5. 第三次调用useState。 React看到位置2为空,同样创建新状态,将nextHook递增到3,并返回[isPlaying,setPlaying]

现在,hooks 数组中有3个hook,渲染完成。 下一次渲染会发生什么?

  1. React需要重新渲染组件, 由于 React 之前已经看过这个组件,它已经有了元数据关联。
  2. ReactnextHook索引重置为0,并调用组件。
  3. 调用useState,React查看索引0处的hooks数组,并发现它已经在该槽中有一个hook。,所以无需重新创建一个,它将nextHook推进到索引1并返回[volume,setVolume],其中volume仍设置为80
  4. 再次调用useState。 这次,nextHook1,所以React检查数组的索引1。同样,hook 已经存在,所以它递增nextHook并返回[position,setPosition]
  5. 第三次调用useState,我想你知道现在发生了什么。

就是这样了,知道了原理,看起来也就不那么神奇了, 但它确实依赖于一些规则,所以才有使用 Hooks 规则。

Hooks 的规则

自定义 hooks 函数只需要遵守规则 3 :它们的名称必须以“use”为前缀。

例如,我们可以从AudioPlayer组件中将3个状态提取到自己的自定义钩子中:

    function AudioPlayer() 
      // Extract these 3 pieces of state:
      const [volume, setVolume] = useState(80);
      const [position, setPosition] = useState(0);
      const [isPlaying, setPlaying] = useState(false);
    
      // < beautiful audio player goes here >
    

因此,咱们可以创建一个专门处理这些状态的新函数,并使用一些额外的方法返回一个对象,以便更容易启动和停止播放,例如:

    function usePlayerState(lengthOfClip) 
      const [volume, setVolume] = useState(80);
      const [position, setPosition] = useState(0);
      const [isPlaying, setPlaying] = useState(false);
    
      const stop = () => 
        setPlaying(false);
        setPosition(0);
      
    
      const start = () => 
        setPlaying(true);
      
    
      return 
        volume,
        position,
        isPlaying,
        setVolume,
        setPosition,
        start,
        stop
      ;
    

像这样提取状态的一个好处是可以将相关的逻辑和行为组合在一起。可以提取一组状态和相关事件处理程序以及其他更新逻辑,这不仅可以清理组件代码,还可以使这些逻辑和行为可重用。

另外,通过在自定义hooks中调用自定义hooks,可以将hooks组合在一起。hooks只是函数,当然,函数可以调用其他函数。

总结

Hooks 提供了一种新的方式来处理React中的问题,其中的思想是很有意思且新奇的。

React团队整合了一组很棒的文档和一个常见问题解答,从是否需要重写所有的类组件到钩Hooks是否因为在渲染中创建函数而变慢? 以及两者之间的所有东西,所以一定要看看。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://daveceddia.com/intro-to-hooks/

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了20亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用

以上是关于Vue3中使用hooks,hooks究竟是个啥?如何理解的主要内容,如果未能解决你的问题,请参考以下文章

淘宝Tedis组件究竟是个啥

淘宝Tedis组件究竟是个啥

vue3中hooks的介绍及用法

荣耀60 SE再上新 运存究竟是个啥?

对称加密非对称加密公钥私钥究竟是个啥

以太坊虚拟机EVM究竟是个啥