1.前言

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

2.vue3 hooks

2.1 为什么vue3 可以使用hooks

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

2.2使用hooks的好处与优点

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

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

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>