React Hooks的使用

170 阅读3分钟

useState

使用状态

const [n,setN] = React.useState(0)
const [user,setUser] = React.useState({name:'Jack'})

注意事项

  • 不可局部更新

    如果 state 是一个对象,setState 不会自动合并属性

    import React, { useState } from "react";
    import "./styles.css";
    
    export default function App() {
      const [user,setUser] = useState({name:'Jack',age:18})
      const Click = ()=>{
        setUser({
          name:'Tom'
        })
      }
      return (
        <div className="App">
          <h1>{user.name}</h1>
          <h2>{user.age}</h2>
          <button onClick={Click}>Click</button>
        </div>
      );
    }
    

    效果

    可以用 ... 操作符解决

  • 地址要改变

    setState(obj) 如果 obj 地址不变,React 就会认为数据没有变化

    import React, { useState } from "react";
    import "./styles.css";
    
    export default function App() {
      const [user,setUser] = useState({name:'Jack',age:18})
      const Click = ()=>{
      	console.log('点击了')
      	user.name = 'Tom'
        setUser(user)
      }
      return (
        <div className="App">
          <h1>{user.name}</h1>
          <h2>{user.age}</h2>
          <button onClick={Click}>Click</button>
        </div>
      );
    }
    

    效果

  • useState 接受函数

    该函数返回初识 state,且只执行一次,可以减少多余的计算过程

    const [user,setUser] = useState(()=>({name:'Jack',age:9+9}))
    
  • setN 接受函数

    当执行下面的代码的时候,得出来的结果并不是如我们想的一样是2,而是1

    import React, { useState } from "react";
    
    export default function App() {
      const [n,setN] = useState(0)
      const Click = ()=>{
      	setN(n+1)
        setN(n+1)
      }
      return (
        <div className="App">
          <h1>{n}</h1>
          <button onClick={Click}>+1</button>
        </div>
      );
    }
    

    效果

    setN 接受函数可以解决如上问题

    const Click = ()=>{
      	setN(i => i+1)
        setN(x => x+1)
      }
    

    效果

useReducer

useReducer 是用来践行Flux/Redux思想

  1. 创建初始值 initialState
  2. 创建所有操作 reducer(state,action)
  3. 传给 useReducer 得到读和写 API
  4. 调用 写({type:'操作类型'})
import React, { useReducer } from "react";
import "./styles.css";
const initial = {
  n:0
}
const reducer=(state,action)=>{
  if(action.type==='add'){
    return {n:state.n+action.number}
  }else if(action.type==='sub'){
    return {n:state.n-action.number}
  }else{
    throw new Error("unkown type")
  }
}
export default function App() {
  const [state,dispatch]=useReducer(reducer,initial)
  const {n}=state
  const Click = ()=>{
    dispatch({type:'add',number:2})
  }
  const Click2 = ()=>{
    dispatch({type:'sub',number:1})
  }
  return (
    <div className="App">
      <h1>{n}</h1>
      <button onClick={Click}>+2</button>
      <button onClick={Click2}>-1</button>
    </div>
  );
}

效果

总的来说,useReduceruseState 的复杂版,注意useReducer 也不能自动合并属性

useContext

useContext 是局部的全局变量

  1. 使用 C =createContext(initial) 创建上下文
  2. 使用 <C.provider> 圈定上下文的作用域
  3. 在作用域内,使用 useContext(C) 来使用上下文
import React, { createContext, useState, useContext } from "react";
import "./styles.css";

const C = createContext(null)
export default function App() { 
  const [n,setN] = useState(0)
  return (
    <C.Provider value={{n,setN}}>
      <div className="App">
        <Father/>
      </div>
    </C.Provider>
  );
}

function Father(){
  return(
    <div>
      Father
      <Son />
    </div>
  )
}
function Son(){
  const {n,setN} = useContext(C)
  const Click=()=>{
    setN(n+1)
  }
  return(
    <div>
      <p>Son n:{n}</p>
      <button onClick={Click}>+1</button>
    </div>
  )
}

效果

注意:useContext 不是响应式的,在另一个模块改变C的值,另一个模块不会感知到这个变化,改变属性时,是通知 App 重新渲染的

useEffect

函数组件中没有类组件中的生命周期,于是就有了 useEffect API 的出现,利用该 API 模拟生命周期

模拟 constructor

函数组件执行的时候,就相当于 constructor

模拟 shouldComponentUpdate

用 React.memo 和 useMome 可以解决

模拟 render

函数组件的返回值就是 render 的返回值

模拟 componentDidMount

useEffect(()=>{ 
	console.log('第一次渲染') 
},[])

模拟 componentDidUpdate

useEffect(()=>{
	console.log('任意属性变了') 
})

useEffect(()=>{ 
	console.log('属性n变了') 
},[n])

模拟 componentWillUnmount

useEffect(()=>{ 
	console.log('第一次渲染') 
	return ()=>{
    	console.log('组件要销毁了')
    }
})

可以出现多个 useEffect,会按照出现顺序依次执行

useLayoutEffect

useLayoutEffect 在浏览器渲染前执行

特点:

  • useLayoutEffect 总是比 useEffect 先执行
  • useLayoutEffect 最好影响了 Layout

useMemo

转到 React useMemo的使用

useRef

绑定 DOM

import React, { useRef, useEffect } from "react";
import "./styles.css";

export default function App() {
  const myRef = useRef(null)
  useEffect(()=>{
    myRef.current.value='输入框'
  },[])
  return (
    <div className="App">
     <input type="text" ref={myRef} />
    </div>
  );
}

效果

父组件调用子组件方法

import React, { useState, useRef, useImperativeHandle } from "react";
import "./styles.css";

export default function App() {
  const myRef = useRef()
  const Click =()=>{
    myRef.current.add()
  }
  return (
    <div className="App">
     <div>Father</div>
     <button onClick={Click}>+1</button>
     <Son ref={myRef}/>
    </div>
  );
}
const Son = React.forwardRef((props,ref)=>{
  const [n,setN]=useState(0)
  useImperativeHandle(ref,()=>({
    add:()=>{
      setN(i=>i+1)
    }
  }))
  return(
    <div>
      <div>Son: {n}</div>
    </div>
  )
})

效果

注意:

  • 由于函数组件的 props 不包含 ref,需要使用 React.forwardRef,如上
  • useImperativeHandle 可以自定义 ref

储存以前的值

import React, { useState, useEffect, useRef } from "react";
import "./styles.css";


export default function App() {
  console.log('App执行了')
  const [n,setN]=useState(0)
  const count = useRef(0)
  const Click =()=>{
    setN(i=>i+1)
  }
  useEffect(()=>{
    count.current+=1
    console.log(count.current)
  })
  return (
    <div className="App">
     <div>{n}</div>
     <button onClick={Click}>+1</button>
    </div>
  );
}

效果