入门react-hooks

171 阅读7分钟

react-hooks出来了那么久,看过一点文章,从来没用过,先会用再说把

基础hooks

useState

之前我们写纯UI组件的时候,通常都是写成无状态组件,就是函数的形式,但是有了useSate之后,函数组件也可以有自己的状态啦,话不多说,看怎么用

//引入useState
import {useState} from 'react';

function App() {
//声明状态和改变状态的方法
const [count, setCount] = useState(0);
  return (  
      <div>
          <span>patent-count:{count}</span>
    </div>
  );
}

注意事项:
1、状态声明必须放在函数的最外层,不能在条件语句里面,因为react是通过声明顺序确定状态
2、跟setState不同的是,setState会自动合并对象,而useState要自己去合并

  • setState
state = {
name: 'tony',
age: 18
}
this.setState({age: 20})
  • useState
const [people, setPeople] = useState({name: 'tony',age: 18});

setPeople({
...people,
age: 20
})

useEffect

useEffect官方文档叫副作用,因为react组件的工作就是渲染UI嘛,副作用我理解就是与渲染无关的,包括数据请求手动操作DOM订阅一些事件等等,有了useEffect钩子, 我们可以在函数组件中完成类似类组件中生命周期函数的功能,useEffect接收两个参数,函数依赖数组的不同组合,实现不同的功能

  • 不传第二个参数
//组价初始化,更新都执行副作用
useEffect(() => {
  console.log('useEffect run');
})
//如果用生命周期函数实现同样的功能,会比较麻烦一点
componentDidMount() {    
    console.log('useEffect run');  
}  
componentDidUpdate() {    
    console.log('useEffect run');  
}

  • 第二个参数为空数组
//只在组件初始化执行一次副作用
useEffect(() => {
    console.log('useEffect run once');
  }, [])
//使用生命周期函数,
componentDidMount() { 
    console.log('useEffect run once');  
} 
  • 第二个数组参数不为空
//依赖项count改变时执行副作用
  useEffect(() => {
    console.log(`useEffect run by count`);
  }, [count])
  • 返回一个函数,解绑副作用
   //每次组件更新,都会执行先解绑,再绑定的逻辑
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(this.props.id,this.handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(this.props.id,this.handleStatusChange);
    }
  })
  //这样如果订阅时间的依赖id变化,就能重新绑定正确的事件,如果使用生命周期钩子,我们想实现同样的功能,就要增加`componentDidUpdate`逻辑
  
componentDidMount() {    
     ChatAPI.subscribeToFriendStatus(this.props.id,this.handleStatusChange);  
}  
componentDidUpdate() {    
     ChatAPI.subscribeToFriendStatus(this.props.id,this.handleStatusChange);  
}
componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(this.props.id,this.handleStatusChange);
}
  

注意事项(抄自官网):
与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 [useLayoutEffect]Hook 供你使用,其 API 与 useEffect 相同。

useContext

在使用useContext之前,我们先回顾一下之前我们是如何使用react提供的context能力,我们想在组件嵌套层级很深的情况下传递属性,如果不想通过props层层传递,这时候就是context的使用场景,当然redux也能,但是这里我们学习context,就只看一下context怎么用

//父组件App.js
import { createContext} from 'react';
//创建context对象,并导出
const CountContext = createContext(0);
export {CountContext};
function App() {
    return(
        //包裹子组件
        <CountContext.Provider value='123'>
         <Child />
      </CountContext.Provider>
    )
}

//层级很深的子组件
//第一种用法
import {CountContext} from '../App.js';
class GrandChild extends component(){
    
    static contextType = CountContext
    return(
        <div className='grand-child'>
            {this.context}
        </div>
    )
}
//第二种用法
import {CountContext} from '../App.js';
class GrandChild extends component(){
    
    return(
       <CountContext.Consumer>
            {value => /* 基于 context 值进行渲染*/}
      </CountContext.Consumer>
    )
}

如果对以上context的用法已经熟悉了,那么useContext钩子,只不过是增加了一种使用context的方式

//父组件App.js,定义阶段跟之前一毛一样
import { createContext} from 'react';
//创建context对象,并导出
const CountContext = createContext(0);
export {CountContext};
function App() {
    return(
        //包裹子组件
        <CountContext.Provider value='123'>
         <Child />
      </CountContext.Provider>
    )
}


//子组件中,通过useContext接收值
import {CountContext} from '../App.js';
import { useContext } from 'react';
class GrandChild extends component(){
    const count = useContext(CountContext); 
    return(
       <div>{count}</div>
    )
}

注意事项:
1、子组件会优先使用离自己最近的父组件中定义的context
2、即使中间的组件没有触发重新渲染,父组件的context value发生变化,使用的子孙组件也会触发更新

其他Hooks

memo、useMemo、useCallback

这三个东西放在一起说,因为他们三个做的事情我觉得都一样,分开奖太啰嗦了,,他们都是做缓存的,换句话说,就是控制组件,只在正确的时间重新渲染

/**
*memo是一个高阶函数,用来缓存组件
*第二个参数是一个函数,接受旧值和新值,返回布尔值
*可以通过比较新旧值,决定组件是否重新渲染
*/
import { memo } from 'react';
const isEqual = (prevProps, nextProps) => {
    return prevProps.count === nextProps.count;
}
const Child = memo((props) => {
    const { children, count } = props;
    console.log('child render')
    return (
        <div>{`${children}:${count}`}</div>
    )
}, isEqual)


/**
*useMemo用来缓存变量,重新计算新值变化,组件才会重新渲染
*接收一个函数,和一个依赖项数组,如果不传依赖项,每次都会重新计算值
*
*/
import { useMemo } from 'react';
const Child = (props) => {
    const { count } = props;
    const newCount = useMemo(() => {
        return count;
    },[count])
    console.log('child render');
    return (
        <div>
         {newCount}
        </div>
    )
}

/**
*useCallback用来缓存函数
*接收一个函数,第二个参数为依赖项数组
*如果依赖项不变化,传给子组件就是同一个函数,子组件不会重新渲染
*/
//父组件
import { useCallback } from 'react';
 function App() {
     const handleShowInputValue = useCallback((e) =>{
         console.log(e.target.value)
     })
     return(
         <Child  onShowInputValue={handleShowInputValue}></Child>
     )
 }
 
//子组件
const Child = (props) => {
    const { onShowInputValue} = props;
    console.log('child render');//不会重复执行
    return (
        <div className='child'>
            <input type="text" onChange={onShowInputValue}/>
        </div>
    )
}

useReducer

这个钩子乍一看,以为是代替redux的,但是实际上他是useState的替代方案,当更新逻辑比较复杂,或者下一个state依赖于上一个state,使用useReducer, 比如下面的场景:

//对一个状态要做很多不同逻辑,使用useReducer更加优雅
import {useReducer} from 'react';
//定义一个reducer函数,接收state对象,和action
function reducer(state,action){
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    ...
    default:
      throw new Error();
  }
}

function App() {
    //声名状态
    const [state, dispatch] = useReducer(reducer, {count:0});
    return(
     <>
      <span>{state.count}</span>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useReducer使用起来跟redux有点类似,但是他和redux有本质的区别:

  • useReducer是单个组件的状态管理,组件通讯还是需要props
  • redux是全局状态管理,多组件共享数据

useRef

用过react的一看这个API,大概也能猜到他是干什么的,至少可以知道他可以手动操作DOM节点吧,那在没有hooks之前,还有个creatRef呢,到底有啥区别

//点击button获取焦点
//使用createRef实现
import {createRef} from 'react';
function App() {
    const inputEl = createRef();
    const handleFoucs () => {
        inputEl.current.foucs();
    }
    return(
        <>
            <input ref={inputEl}></input>
            <button onClick={handleFoucs}>Foucus</button>
        </>
    )
}

//使用useRef实现同样的功能
import {useRef} from 'react';
function App() {
    const inputEl = useRef();
    const handleFoucs () => {
        inputEl.current.foucs();
    }
    return(
        <>
            <input ref={inputEl}></input>
            <button onClick={handleFoucs}>Foucus</button>
        </>
    )
}

从上面的例子看,useRef和createRef作用完全一样,那么为什么还要整个useRef,所以重点是useRef不一样的地方

createRef 与 useRef 的区别

useRef 在 react hook 中的作用, 正如官网说的, 它像一个变量, 它就像一个盒子, 你可以存放任何东西. createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用

考虑如下场景:

function App() {
    const [count, setCount] = useState(0)
    const handleALert () => {
        setTimeout(() => {
            alert(count)
        }, 3000)
    }
    return(
        <>
            <button onClick={setCount(count+1)}>add</button>
            <button onClick={handleALert}>alert</button>
        </>
    )
}

当我们更新状态的时候, React 会重新渲染组件, 每一次渲染都会拿到独立的 count 状态, 并重新渲染一个 handleALert 函数. 每一个 handleALert 里面都有它自己的 count, alert 出来的值, 就是当时点击时的 count 值.

使用useRef实时获取count值

function App() {
    const [count, setCount] = useState(0)
    const latestCount = useRef();
    useEffect(() => {
        latestCount.current = count;
    })
    const handleALert () => {
        setTimeout(() => {
            alert(latestCount.current)
        }, 3000)
    }
    return(
        <>
            <button onClick={setCount(count+1)}>add</button>
            <button onClick={handleALert}>alert</button>
        </>
    )
}

useRef 每次都会返回同一个引用, 所以在 useEffect 中修改的时候 ,在 alert 中也会同时被修改. 这样子, 点击的时候就可以弹出实时的 count 了

上面的问题解决了, 我们继续, 我们希望在界面上显示出上一个 count 的值. 上代码


function App() {
    const [count, setCount] = useState(0)
    const preCount = useRef();
    useEffect(() => {
        preCount.current = count;
    })
    return(
        <>
            <span>{preCount.current}</span>
            <span>{count}</span>
            <button onClick={setCount(count+1)}>click me</button>
        </>
    )
}

useRef创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...}对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

总结

  • useRef 不仅仅是用来管理 DOM ref 的,它可以存放任何变量.
  • useRef 可以很好的解决闭包带来的不方便性. 你可以在各种库中看到它的身影, 比如 react-use 中的 useInterval , usePrevious …… 值得注意的是,当 useRef 的内容发生变化时,它不会通知您。更改.current属性不会导致重新呈现。 因为他一直是一个引用

自定义hook

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中

自定义 Hook 是一个函数,其名称以 “use” 开头,里面是不能使用默认的hook函数内部可以调用其他的 Hook 比如:获取上一个值, 这在实际场景中并不少, 我们尝试把它封装成自定义 hook

import {useRef,useEffect } from 'react';
const usePrevCount = state => {
    const ref = useRef();
    useEffect(()=>{
        ref.current = state;
    })
    return ref.current;
}
export default usePrevCount;