React Hooks的使用

391 阅读6分钟

useState

  • 使用useState可以让函数式组件也能拥有状态(state)
  • 语法: const [xxx, setXxx] = React.useState(initValue) initValue是初始值
  • useState()说明: 参数: 第一次初始化指定的值在内部作缓存 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
  • setXxx()2种写法:
// 1 . setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
const [count,setCount] = React.useState(0)
function add(){
    setCount(count+1)
}
// 2 . 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
const [count,setCount] = React.useState(0)
function add(){
    setCount(value=>value+1)
}

useEffect

  • 使用useEffect,能让函数式组件完成类似生命周期的功能
语法和说明: 
        useEffect(() => { 
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行
            // 相当于componentWillUnmount
          }
        }, [stateValue]) 
        // 如果指定的是[], 回调函数只会在第一次render()后执行
        // 如果没有的话挂载和每次更新都会执行
  • useEffect第一个参数的return后面是一个函数,该函数相当于componentWillUnmount
  • useEffect第二个参数不填的话,那么每次渲染都会执行,相当于componentDidUpdate
  • useEffect第二个参数是空数组的话,相当于componentDidMount
  • useEffect第二个参数可以指定某个属性变化了之后再执行
const [count, setcount] =  React.useState(0)
const [name, setname] =  React.useState('zxx')
React.useEffect(()=>{
	console.log('count变化了才执行,name变化不执行')
},[count])

useLayoutEffect

  • 用法和useEffect一样,但是效果有略微的差别
  • useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM更新
  • useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新
  • useLayoutEffect的执行会先于useEffect,所以想要修改组件元素样式建议用useLayoutEffect

useContext

  • useContext可以帮助我们跨越组件层级直接传递变量,实现共享
  • 首先在父组件中
// 想要通过context传递两个数据
import React ,{useContext}from 'react'
const CountContext = React.createContext()  
const NameContext = React.createContext()  
export default function Father() {
  
  return (
    <div>
      <CountContext.Provider value={15}>
        <NameContext.Provider value={'sss'}>
        <Son/>
        </NameContext.Provider>
      </CountContext.Provider>
    </div>
  )
}
  • 在后代组件中接受
function Son(){
   //useContext 的参数必须是 context 对象本身,没有的话就自己导入
  const countInfo = useContext(CountContext)
  const nameInfo = useContext(NameContext)
  return (
    <div>
      <h2>Header组件</h2>
      <p>{countInfo}</p>
      <p>{nameInfo}</p>
    </div>
  )
}

useReducer

useReducer仅仅是useState的一种替代方案。如果state处理逻辑比较复杂,我们可以用useReducer来拆分

  • const [状态,dispatch] = useReducer(reducer,初始化状态的值)
import React ,{useReducer}from 'react'
// 第一步创建reducer,接受两个参数state和action
function reducer(state,action){
  switch (action.type) {
    case 'increment':
      return {...state,num:state.num+1}
    case 'decrement':
      return {...state,num:state.num-1}
    default:
      throw new Error();
  }
}
function Home(){
  // 使用useReducer
  const [state,dispatch] = useReducer(reducer,{num:0})
  return (
    <div>
      <h2>Header数据:{state.num}</h2>
      {/*派发action*/}
      <button onClick={()=>dispatch({type:'decrement'})}>-1</button>
      <button onClick={()=>dispatch({type:'increment'})}>+1</button>
    </div>
  )
}
function About(){
  const [state,dispatch] = useReducer(reducer,{num:25})
  return(
    <>
      <h2>About数据:{state.num}</h2>
      <button onClick={()=>dispatch({type:'decrement'})}>-1</button>
      <button onClick={()=>dispatch({type:'increment'})}>+1</button>
    </>

  )
}
export default function App() {
  return (
    <div>
      <Home></Home>
      <About></About>
    </div>
  )
}

useCallback

useCallback实际的目的是为了进行性能优化

  • useCallback会返回一个函数的memoized值
  • 在依赖不变的情况下,多次定义的时候,返回的值是相同的

搭配memo使用:当不使用memo的时候,父组件重新渲染,子组件必定重新渲染,所以使用memo(子组件),可以避免这个情况发生

import React,{useState,memo,useCallback} from 'react'
// 没使用useCallback的时候,每次增加或者减少,所有的组件都会重新渲染
function Home(props){
  console.log("HOME渲染了");
  return(
    <div>
      <p>Home</p>
      <button onClick={()=>props.handle()}>减小</button>
    </div>
  )
}
function About(props){
  console.log("ABOUT渲染了");
  return(
    <div>
      <p>About</p>
      <button onClick={()=>props.handle()}>增加</button>
    </div>
  )
}
const MemoHome = memo(Home)
const MemoAbout = memo(About)
export default function App() {
  console.log("APP渲染了");
  const [numState,setNum] = useState(12)
  const [ageState,setAge] = useState(2)
   // 只有ageState变化时候,sub这个函数才会变化
  const sub = useCallback(
    () => {
      setAge(ageState-1)
    },[ageState],
  )
  // 只有numState变化时候,add这个函数才会变化
  const add = useCallback(
    () => {
      setNum(numState+2)
    },[numState],
  )
  return (
    <div>
      <p>num:{numState}</p>
      <p>age:{ageState}</p>
      <hr />
      <MemoHome handle={sub}/>
      <MemoAbout handle={add}/>
    </div>
  )

使用场景:将一个组件中的函数,传递给子组件进行回调使用时,使用useCallback对函数进行处理

useMemo

useCallback实际的目的也是为了进行性能优化

  • 使用场景一:
// 每次点击按钮Home组件都会重新渲染,因为App组件重新渲染导致person的内存地址变化
import React, { memo,useState } from 'react'
const Home = memo((props)=>{
  console.log('Home渲染');
  return (
    <h2>name:{props.person.name}--age:{props.person.age}</h2>
  )
})
export default function App() {
  const [show, setshow] = useState(false)
  let person = {name:'zx',age:15}
  return (
    <div>
      <Home person={person}/>
      <button onClick={()=>setshow(!show)}>changeShow</button>
    </div>
  )
}
  • 场景一解决方法:
import React, { memo,useMemo,useState} from 'react'
const Home = memo((props)=>{
  console.log('Home渲染');
  return (
    <h2>name:{props.person.name}--age:{props.person.age}</h2>
  )
})
export default function App() {
  const [show, setshow] = useState(false)
  let person = useMemo(() => {
    return {name:'zx',age:15}
  }, [])
  return (
    <div>
      <Home person={person}/>
      <button onClick={()=>setshow(!show)}>changeShow</button>
    </div>
  )
}
  • 使用场景二
  • 如果没有使用useMemo,返回函数的时候,每次App重新渲染都会让getData这个函数重新调用
import React,{useMemo, useState} from 'react'
function getData(){
  console.log('getData被调用了');
  let data = 0
  for(let i=0;i<1000;i++){
    data+=i
  }
  return data
}
export default function App() {
  console.log('App组件被渲染了');
  const [numState,setNum] = useState(10)
  const [ageState] = useState(55)
  // const data = getData()
  // 通过useMemo可以使得getData不会被重复调用
  const data = useMemo(()=>{
    return getData()
  },[])
  return (
    <div>
      <h2>num:{numState}</h2>
      <h2>age:{ageState}</h2>
      {/* 修改numState的时候,getData会被重新调用 */}
      <button onClick={()=>{setNum(numState+1)}}>修改num</button>
      <h2>data:{data}</h2>
    </div>
  )
}

useRef

useRef可以在函数组件中存储/查找组件内的标签或任意其它数据
  • 用法一:引入DOM(或者组件,但是必须是class组件)元素,保存标签对象,功能用法与React.createRef()类似
  • const 容器的名字= useRef()
import React,{useRef} from 'react'
export default function Demo() {
  // 创建ref容器
  const myRef = useRef()
  function getMsg(){
    // 打印通过ref容器获取的元素里面的value值
    alert(myRef.current.value)
  }
  return (
    <div>
      <input type="text" ref={myRef}/>
      <button onClick={getMsg}>获取input信息</button>
    </div>
  )
}
  • 用法二:保存一个数据,这个对象在整个生命周期中可以保持不变
import React,{useRef} from 'react'
export default function Demo() {
  // myref去保存数值,除了手动去修改,要不是不会发生变化的
  const myRef = useRef(20)
  function getMsg(){
    // 打印通过ref容器获取的元素里面的value值
    console.log(myRef.current) //20 
  }
  return (
    <div>
      <input type="text" />
      <button onClick={getMsg}>获取信息</button>
    </div>
  )
}

useImperativeHandle

使用useImperativeHandle之前,首先得了解一下forwardRef

forwardRef

  • 函数组件需要使用forwardRef包装才能使用useRef
  • 包装之后的函数组件可以接受两个参数,第二个参数就是ref,自己再手动绑定到你需要的元素上
import React, { useRef,forwardRef } from 'react'

function HInput(props,ref){
  // 使用了forwardRef之后,他可以接受两个参数,第二个参数是ref
  return (
    <div>
      <input type="text" ref={ref}/>
    </div>
  )
}
// 函数组件需要使用forwardRef包装才能使用useRef
const NewHInput = forwardRef(HInput)
export default function App()  { 
  const myInput = useRef()
  function change(){
    myInput.current.focus()
  }
    return (
      <div>
        <NewHInput ref={myInput}/>
        <button onClick={change}>聚焦子组件input框</button>
      </div>
    )
}

父组件就能获取到子组件的dom,可以为所欲为.

useImperativeHandle

单纯使用forwardRef,也能满足需求,但是这样父组件就能获取到子组件的dom,所以我们想限制一下父组件的权限,只传递父组件能操作子组件的具体方法.

  • 用法:useImperativeHandle(传递的ref,是一个函数,然后返回一个对象,对象的key就是函数名,value就是函数)
import React, { useRef,forwardRef,useImperativeHandle } from 'react'

function HInput(props,ref){
  useImperativeHandle(ref,()=>({
    focus:()=>{
      console.log('focus');
    }
  }))
    
  return (
    <div>
      <input type="text"/>
    </div>
  )
}
const NewHInput = forwardRef(HInput)
export default function App()  { 
  const myInput = useRef()
  function change(){
    myInput.current.focus()
  }
    return (
      <div>
        <NewHInput ref={myInput}/>
        {/* 点击按钮就会执行自己写的foucs方法 */}
        <button onClick={change}>聚焦子组件input框</button>
      </div>
    )
}

自定义Hook

  • 自定义hook本质上就是一种函数代码逻辑的抽取。
  • 普通函数名前加上use就能使用hook了
  • 比如有这么一个需求所有的组件挂载更新和卸载都打印一句话
import React, { useState,useEffect } from 'react'
// 普通函数加上use就能使用hooks了,use后面第一个字母必须大写
function useAddEventListener(component){
  useEffect(() => {
    console.log(`${component}组件挂载更新`);
    return () => {
      console.log(`${component}组件卸载`);
    }
  })
}
function Home(){
  useAddEventListener('Home')
  return (
    <div>Home</div>
  )
}
function About(){
  useAddEventListener('About')
  return (
    <div>About</div>
  )
}
export default function App() {
  const [isShow,setIsShow] = useState(true)
  return (
    <div>
      {isShow &&  <Home/>}
      {isShow &&  <About/>}
      <button onClick={()=>{setIsShow(!isShow)}}>切换</button>
    </div>
  )
}