hook(钩住),表示拦截操作,可以在某些操作执行时拦截住(钩住),执行hook函数的逻辑,
在react中,hook函数一般是use开头,然后提供一些方法api,也就是说你可以理解成react中的hook就是一套便于操作的api
useState钩子是一个提供响应式状态的方法,
const [show, setShow] = useState(false)
它接受一个任意值,返回一个数组,里面包含一个由任意值生成的响应式变量,和这个响应式变量的修改方法,
此处示例可以参考:react基础语法,模板语法,ui渲染,jsx,useState状态管理-CSDN博客
useReducer钩子可以实现状态处理的统一管理,状态处理是指操作状态的方法,
useReducer和usestate一样可以定义响应式状态和状态处理方法,不过相比useState,它可以提供逻辑更加明显,处理方式更多的方法,而不是单单一个修改覆盖的set
useReducer,接受一个函数方法和一个作为状态的任意值,返回一个数组,包含了状态值和一个触发器函数,(在用法上和useState类似)
- const [state,dispatch] = useReducer(reducer,value)
- //接受一个状态处理函数reducer和一个初始值value
- //返回状态值state和触发器函数dispatch
- //dispatch接受一个对象action,包含了type属性,用来触发reducer中指定的逻辑
- import { useReducer } from "react"
-
- function reducer(state: number, action: { type: string }) {
- // 接受两个参数,state状态值,触发对象(包含type属性)
- switch (action.type) {//根据type触发不同逻辑
- case 'increment':
- return state + 1;
- case 'decrement':
- return state - 1;
- default:
- return state;
- }
- }
-
- export default function home() {
- const [state, dispatch] = useReducer(reducer, 0);
- //接受一个状态处理函数和一个初始值
- //返回状态值state和触发器函数dispatch
- //dispatch接受一个对象action,包含了type属性,用来触发reducer中指定的逻辑
- return (
- <div>
- <h1>homeh1>
- <div>
- <button onClick={() => dispatch({ type: 'decrement' })}>-button>
- <span>{state}span>
- <button onClick={() => dispatch({ type: 'increment' })}>+button>
- div>
- div>
- )
- }
这样所有的逻辑都在集中在reducer中,并通过dispatch触发,相比于useState更好管理
useEffect 副作用钩子函数,它接受两个参数,callback:回调函数 ,监听值变化时触发执行的副作用,若没有监听值,则会在每次组件渲染后触发(容易出现死循环),data:数组(可选),被监听的值,
useEffect(()=>{},[])
注意:当传入的 的值是一个空数组[ ]时,此时副作用函数只会在初始渲染时触发(开发环境下会触发两次,生产环境下触发一次,官方的说法是,便于开发是暴露出可能出现的渲染错误)
- useEffect(() => {
- console.log(state,'state变化了')
- },[state])//监听state变化,当state变化时触发回调函数
当state的值变化是会触发回调打印结果,这里注意值还没有变化时回调就自动触发了两次,如果不想要它在初始化时执行,应该在逻辑种设置条件,例如
- const [done, setDone] = useState(false)
- useEffect(() => {
- if(done){
- console.log(state,'state变化了')
- }else{
- setDone(true)
- }
- },[state])//监听state变化,当state变化时触发回调函数
useEffect钩子是跟随组件的;在组件挂载时,它会触发渲染的回调,在组件更新时,作为交互响应,在组件移除卸载后,它也会失效
这个三个生命周期正好对应了3种效果
- 挂载时:一般用于网络请求,组件挂载后开始请求并渲染数据
- 更新时:触发响应式,引起页面变化
- 卸载时:清除副作用函数
useRef接受一个任意值,返回一个具有current属性的对象,current内存放了接受的任意值,
- const ref = useRef(value)
-
- const ref = useRef(0)
可以看到ref就是一个普通的对象,它和useState不同,它可以任意修改current属性中的值,同时并不会触发页面的更新渲染,这是一个非响应的值,就像普通的变量一样;
虽然不会触发页面的更新,但是ref会保留修改的值,当页面被更新时,ref也会变成最新的值,ref不会触发更新,但它可以被‘更新’
- const pre = useRef(0);
- // console.log(pre);
-
- useEffect(() => {
- if(done){
- pre.current = state
- console.log('页面变化了',state,pre.current)
- }else{
- setDone(true)
- }
- },[state])//监听state变化,当state变化时触发回调函数
这里当state改变时触发页面渲染,将state的值赋值给pre,每次页面重新渲染后,ref就会变成上一次的赋值,而最近一次赋值是指页面更新后才触发的,所以pre虽然和state是相等的,但在页面上永远少更新一次,pre是被更新而不是触发更新
ref可以作为dom的属性,用来获取dom实例,如果你熟悉vue3的ref,那么它们是用法几乎是一致的
- const spanRef = useRef(null)
- console.log(spanRef);
-
- <span ref={spanRef}>上一次的值:{pre.current}span>
spanRef的current属性存放的就是span标签实例,注意这里的spanRef是和span中ref属性的spanRef一致的
在react中useRef除了获取Dom,还可以由条件的获取子组件实例,并使用器内部的方法,这需要子组件将自身内部暴露出来,
- forwardRef :
- 接受一个回调函数(react组件),回调函数拥有props(一般的属性)和ref(父组件获取子组件实例的属性),返回一个react组件
- useImperativeHandle
- 接受两个参数,ref 父组件获取子组件实例的属性,回调函数,返回一个对象,内部是暴露出去的方法
- import { forwardRef, useEffect, useImperativeHandle, useReducer, useRef, useState } from "react"
-
-
- // 接受一个回调函数(react组件),回调函数拥有props(一般的属性)和ref(父组件获取子组件实例的属性),返回一个react组件
- const ChildRef = forwardRef( function(props,ref){
-
- useImperativeHandle(ref, () => ({
- // 暴露给子组件的方法
- fn:()=>{
- console.log('我是子组件暴露的方法')
- }
- }))
-
- return <span>我是子组件span>
- })
-
-
- const childRef = useRef(null)
-
- function useFn(){
- childRef.current.fn()
- console.log(childRef)
- }
-
- <ChildRef ref={childRef} />
- <button onClick={useFn}>调用子组件暴露的方法button>
这样在父组件中就使用了子组件的方法,并且获取了子组件实例
useContext(上下文)提供了跨越中间组件传值的功能,
在多级组件中,父子组件传值需要通过props一级一级向下传递,使用useContext钩子就可以略过中间的组件,直接将值传递给目标子组件
使用useContext包括两个部分,创建和引用
createContext: 接受一个任意值,并返回一个存放任意值的context参数
useContext:接受context参数,返回context内的值
- // 创建值进行传递
- const context = createContext(value)
-
- // 在任意组件内使用useContext获取值
- const value = useContext(context);
context.ts
- import { createContext } from "react";
-
- export const context = createContext('初始值')
page.jsx
- import { useContext } from "react";
- import { context } from "../context"
-
- // 使用useContext获取值
- const val = useContext(context);
-
- retrun(
- //...
- <ChildRef ref={childRef} />
- <button onClick={useFn}>调用子组件暴露的方法button>
- <hr />
- <h3>{val}h3>
- )
- // 使用useContext获取值
- const val = useContext(context);
-
- return(
- <>
- <span>我是子组件span>
- <h3>{val}h3>
- >
-
- )
这里两个组件获取的都是直接从context中取到的值,子组件中的值并没有经过父组件,
值得注意的是这个值没有被污染的,每次读取值都是从默认值开始传递(如果父组件的值被修改了,子组件获得的值不会受到影响,没有经过父组件,直接获得的默认值)
如果要在父组件修改子组件接受的值就需要修改子组件获取的context,默认是不经父组件的,通过context.Provider,就可以截取子组件向上获取数据,让子组件从父组件获取context
- <context.Provider value = {val + '经过了父组件的处理'}>
- <ChildRef ref={childRef} />
- context.Provider>
-
- <ChildRef ref={childRef} />
- <ChildRef ref={childRef} />
context.Provider接受一个value属性,它会代替context向下传递
第一个组件经过了父组件获取值,而其他两个子组件是直接获取的默认值没有经过父组件
react官方提供了很多hook,但有时侯对于一些特殊的需求,可以使用自定义的hook;自定义hook 可以将逻辑复用,简化代码结构;自定义hook需要以use开头定义,
不过,没必要对每段重复的代码都提取自定义 Hook,自定义Hook最大的作用应该是减少useEffect,
提供一个页面渲染时触发的hook
- import { useEffect } from "react"
-
- // 提示页面渲染
- export const useTip = () => {
- useEffect(() => {
- console.log('页面渲染了')
- })
- }
- import { useTip } from "../useTip";
-
- // 使用自定义hook
- useTip()
更多关于自定义组件的内容可以参考:使用自定义 Hook 复用逻辑 – React 中文文档
page.tsx
- import { forwardRef, useContext, useEffect, useImperativeHandle, useReducer, useRef, useState } from "react"
- import { context } from "../context"
- import { useTip } from "../useTip";
-
- function reducer(state: number, action: { type: string }) {
- // 接受两个参数,state状态值,触发对象(包含type属性)
- switch (action.type) {//根据type触发不同逻辑
- case 'increment':
- return state + 1;
- case 'decrement':
- return state - 1;
- default:
- return state;
- }
- }
-
- // 接受一个回调函数(react组件),回调函数拥有props(一般的属性)和ref(父组件获取子组件实例的属性),返回一个react组件
- const ChildRef = forwardRef( function(props,ref){
-
- useImperativeHandle(ref, () => ({
- // 暴露给子组件的方法
- fn:()=>{
- console.log('我是子组件暴露的方法')
- }
- }))
-
- // 使用useContext获取值
- const val = useContext(context);
-
- return(
- <>
- <span>我是子组件span>
- <h3>{val}h3>
- >
-
- )
- })
-
- export default function home() {
- const [done, setDone] = useState(false)
- const [state, dispatch] = useReducer(reducer, 0);
- //接受一个状态处理函数和一个初始值
- //返回状态值state和触发器函数dispatch
- //dispatch接受一个对象action,包含了type属性,用来触发reducer中指定的逻辑
-
- const pre = useRef(0);
- // console.log(pre);
-
- const spanRef = useRef(null)
- // console.log(spanRef);
-
- const childRef = useRef(null) as any;
-
- function useFn(){
- childRef.current?.fn()
- console.log(childRef)
- }
-
- // 使用useContext获取值
- const val = useContext(context);
-
- // 使用自定义hook
- useTip()
-
- useEffect(() => {
- if(done){
- pre.current = state
- console.log('页面变化了',state,pre.current)
- }else{
- setDone(true)
- }
- },[state])//监听state变化,当state变化时触发回调函数
-
- return (
- <div>
- <h1>homeh1>
- <div>
- <button onClick={() => dispatch({ type: 'decrement' })}>-button>
- <span>当前值:{state}span>
- <button onClick={() => dispatch({ type: 'increment' })}>+button>
- div>
- <hr />
- <div>
- <span ref={spanRef}>上一次的值:{pre.current}span>
- div>
- <hr />
- <context.Provider value = {val + '经过了父组件的处理'}>
- <ChildRef ref={childRef} />
- context.Provider>
-
- <ChildRef ref={childRef} />
- <ChildRef ref={childRef} />
- <button onClick={useFn}>调用子组件暴露的方法button>
- <hr />
- <h3>{val}h3>
- div>
- )
- }
content.ts
- import { createContext } from "react";
-
- export const context = createContext('初始值')
useTip.ts
- import { useEffect } from "react"
-
- // 提示页面渲染
- export const useTip = () => {
- useEffect(() => {
- console.log('页面渲染了')
- })
- }