React hooks的使用

0.创建react项目

下面使用react的官方脚手架create-react-app创建一个react项目:

  1. 第一步:全局安装脚手架npm install -g create-react-app
  2. 第二步:切换到想要创建项目的目录,使用命令:create-react-app react_project
  3. 第三步:切换目录到项目中:cd react_project
  4. 第四步:启动项目:npm run start

1.ReactHooks简介

Hook是React 16.8的新增特性(react版本在16.8及其以上的,不需要下载hooks库,直接引用就好了)。它可以让你在不编写class的情况下使用state,以及其他的React特性。使用ReactHooks,可以使我们的代码变的简洁而优雅。
我们现在来做一个简单的例子:点击按钮让数字加1
首先用class的方式定义组件

import React, { Component } from 'react'
export default class Example extends Component {
    constructor(props){
        super(props)
        this.state = {count:0}
    }
    render() {
        return (
            <div>
                <p>You clicked {this.state.count} times</p>
                <button onClick={this.addCount.bind(this)}>Click me</button>
            </div>
        )
    }
    addCount(){
        this.setState({count:this.state.count+1})
    }
}

下面用React Hooks来做

import React, {useState} from 'react'
const Example = () => {
    const [count,setCount] = useState(0)
    return(
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>Click me</button>
        </div>
    )
}
export default Example

下面是效果:
count案例效果

2.useState

useState是react自带的一个hook函数,它的作用是用来声明状态变量。useState这个函数接收的参数是状态的初始值,它返回一个数组,这个数组的第0位是当前的状态值第1位是可以改变状态值的方法
再看一下上面的代码:

import React, {useState} from 'react'
const Example = () => {
    //1.使用useState,声明count变量和setCount函数
    //count相当于一个变量了,并且初始值0赋值给了count
    //setCount是一个函数,这个函数用来改变我们的值
    const [count,setCount] = useState(0) //数组解构,不用数组解构可能就要写三行了
    return(
        <div>
            {/* 2.读取count变量 */}
            <p>You clicked {count} times</p>
            {/* 3.使用setCount函数来改变我们count的值 */}
            <button onClick={()=>{setCount(count+1)}}>Click me</button>
        </div>
    )
}
export default Example

3.useEffect

3.1.useEffect代替常用的声明周期函数

在类式组件中是存在生命周期的,但是在函数组件中不存在生命周期的,而使用useEffect可以用来代替我们常用的声明周期函数componentDidMountcomponentDidUpdate。分别在组件第一次渲染后在浏览器控制台打印计数器结果和在每次计数器状态发生变化后打印结果。
使用useEffect的时候需要注意两点:

  1. React首次渲染和之后的每次渲染都会调用一遍useEffect函数,而之前我们要用两个生命周期函数分别表示首次渲染(componentDidMount)和更新导致的重新渲染(componentDidUpdate)。
  2. useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数是异步执行的,而componentDidMountcomponentDidUpdate中的代码都是同步执行的。个人认为这个有好处也有坏处吧,比如我们要根据页面的大小,然后绘制出当前弹出窗口的大小,如果是异步的操作就不好操作了。

下面先展示一下类式组件的写法

import React, {Component} from 'react'
class Example extends Component{
    constructor(props){
        super(props)
        this.state = {count:0}
    }
    componentDidMount(){
        console.log(`componentDidMount=>You clicked ${this.state.count} times`)
    }
    componentDidUpdate(){
        console.log(`componentDidUpdate=>You clicked ${this.state.count} times`)
    }
    render(){
        return(
            <div>
                <p>You clicked {this.state.count} times</p>
                <button onClick={this.addCount.bind(this)}>Click me</button>
            </div> 
        )
    }
    addCount(){
        this.setState({count:this.state.count+1})
    }
}
export default Example

使用useEffect的写法

import React, {useState, useEffect} from 'react'
const Example = () => {
    const [count,setCount] = useState(0) 
    useEffect(() => {
        console.log(`useEffect =>  You clicked ${count} times`)
    })
    return(
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>Click me</button>
        </div>
    )
}
export default Example

3.2.useEffect实现类似componentWillUnmount(组件将要被卸载时执行)

使用路由实现组件的解绑,需要用到useEffect函数里面return一个函数的形式,代替解绑生命周期函数componentWillUnmount组件将要被卸载时执行。但是只使用return一个函数的形式是有性能问题的,它会在页面变化的时候就执行一次useEffect函数,所以这个时候我们需要用到useEffect的第二参数,具体看下面的例子。
父组件:

import React, {useState, useEffect} from 'react'
import {Index} from './index.js'
import {List} from './list.js'
//下面路由的用法,是基于react-router-dom 6.0.1及其以上版本的写法
import {BrowserRouter as Router,Routes, Route, Link} from 'react-router-dom'
const Example = () => {
    const [count,setCount] = useState(0) 
    useEffect(() => {
        console.log(`useEffect =>  You clicked ${count} times`)
        return () => {
            console.log('=============================')
        }
    },[count])
    return(
        <Router>
            <div>
                <p>You clicked {count} times</p>
                <button onClick={()=>{setCount(count+1)}}>Click me</button>
                <ul>
                    <li><Link to="/">首页</Link></li>
                    <li><Link to="/list/">列表</Link></li>
                </ul>
                <Routes>
                    <Route path="/" exact element={<Index/>} />
                    <Route path="/list/" element={<List/>} />
                </Routes>
            </div>
        </Router>
    )
}
export default Example

子组件index:

import {useEffect} from 'react'
export const Index = () => {
    //useEffect的第二参数为空数组
    //表示为只有Index组件被销毁时才执行解绑的动作
    useEffect(() => {
        console.log('useEffect => index页面')
        return () => {
            console.log('index页面解绑了')
        }
    },[])
    return <h2>index page</h2>
}

子组件list:

import {useEffect} from 'react'
export const List = () => {
    useEffect(() => {
        console.log('useEffect => List页面')
    })
    return <h2>list page</h2>
}

useEffect的第二个参数,它是一个数组,数组中可以写入很多状态对应的变量,意思是当状态值发生变化时,我们才进行解绑。但是当传入空数组时,就是当组件被销毁时才进行解绑,这也就实现了componentWillUnmount的生命周期函数。

4.useContext (如果使用状态管理的库可以不用useContext)

useContext主要时用来实现父子组件之间传值的
父组件

import React, {useState,createContext} from 'react'
import {Counter} from './counter'
export const CountContext = createContext()
const Example = () => {
    const [count,setCount] = useState(0) //数组解构,不用数组解构可能就要写三行了
    return(
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>Click me</button>
            <CountContext.Provider value={count}>
                <Counter/>
            </CountContext.Provider>
        </div>
    )
}
export default Example

子组件counter

import React, {useState,createContext} from 'react'
import {Counter} from './counter'
export const CountContext = createContext()
const Example = () => {
    const [count,setCount] = useState(0) //数组解构,不用数组解构可能就要写三行了
    return(
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>Click me</button>
            <CountContext.Provider value={count}>
                <Counter/>
            </CountContext.Provider>
        </div>
    )
}
export default Example

5.useReducer(如果使用状态管理的库可以不用useReducer)

5.1理解什么是reducer

reducer这个函数接收两个参数,一个是状态一个是用来控制业务逻辑的判断参数。下面使用Javascript来实现一个reducer

//useReducer + useContext ~= Redux
//Reducer兴起于Redux,但是和Redux没有什么关系
//useReducer是hooks函数 它增强了普通的js的reducer
const countReducer = (state,action) => {
    switch(action.type){
        case 'add':
            return state+1
        case 'sub':
            return state-1
        default:
            return state
    }
}

5.2使用useReducer

import React, {useReducer} from 'react'
const ReducerDemo = () => {
    const [count,dispatch] = useReducer((state,action)=>{
        switch(action){
            case 'add':
                return state+1
            case 'sub':
                return state-1
            default:
                return state
        }
    },0)
    return (
        <div>
            <h2>现在的分数是{count}</h2>
            <button onClick={()=>{dispatch('add')}}>Increment</button>
            <button onClick={()=>{dispatch('sub')}}>Decrement</button>
        </div>
    )
}
export default ReducerDemo

5.3实现useReducer useContext实现redux的状态管理和状态共享

实现状态全局化并能统一管理,统一个事件的派发
案例:点击按钮切换对应的字体颜色
父组件

//结合useReducer和useContext实现Redux的效果
//使用useContext可以实现访问全局状态,避免了一层层的传递,符合redux的状态全局化
//使用useReducer中的action可以更新复杂的逻辑状态,实现redux中的reducer的这个部分
// 可以让vscode帮我们引入组件
import React from 'react'
import ShowArea from './showArea'
import Buttons from './buttons'
import {Color} from './color'
const Example = () => {
    return (
        <div>
            <Color>
                <ShowArea/>
                <Buttons/>
            </Color>
        </div>
    )
}
export default Example

字体展示区域组件

import React,{useContext} from 'react'
import {ColorContext} from './color'
const ShowArea = () => {
    const {color} = useContext(ColorContext)
    return (<div style={{color:color}}>字体颜色为{color}</div>)
}
export default ShowArea

按钮组件

import React,{useContext} from 'react'
import {ColorContext,UPDATE_COLOR} from './color'
const Buttons = () => {
    const {dispatch} = useContext(ColorContext)
    return (
        <div>
            <button onClick={()=>{dispatch({type:UPDATE_COLOR,color:"red"})}}>红色</button>
            <button onClick={()=>{dispatch({type:UPDATE_COLOR,color:"yellow"})}}>黄色</button>
        </div>
    )
}
export default Buttons

颜色组件

import React, {createContext,useReducer} from 'react'
export const ColorContext = createContext({})
export const UPDATE_COLOR = "UPDATE_COLOR"
const reducer = (state,action) => {
    switch(action.type){
        case UPDATE_COLOR:
            return action.color
        default:
            return state
    }
}
export const Color = props => {
    const [color,dispatch] = useReducer(reducer,'blue')
    return (
        <ColorContext.Provider value={{color,dispatch}}>
            {props.children}
        </ColorContext.Provider>
    )
}

6.useMemo

1)哪些情况下一个组件会重新渲染

  • 组件自己的state变化了
  • 父组件重新渲染了

2)useMemo使用场景

如果一些值的计算量很大,那么可以用useMemo来做一个缓存,只有依赖变化时才会重新计算,而不是每次渲染时都进行计算。

3)useMemo的使用

useMemo主要用来解决使用React hooks产生的无用渲染的性能问题,函数型组件没有shouldComponentUpdate(组件更新前触发),我们没有办法通过组件前的条件来决定组件是否更新。
且在函数组件中,也不再区分mountupdate两个状态,这就意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。useMemouseCallback都是解决上述性能问题的。
其中:useCallback(fn, deps)相当于useMemo(() => fn, deps)
父组件

//useEffect没有shouldComponetUpdate这个生命周期
//举例:父组件里面但状态更新了 ,但子组件没有变化不用更新
//而使用useEffect却会更新,这样子组件里面的方法都要再执行一遍
//如果子组件只是简单的ui界面还好,如果要向后台请求数据,就会极大的影响性能
import React ,{useState} from 'react'
import Child from './child'
const Example = () => {
    const [bingdwendwen,setBingdwendwen] = useState('冰墩墩在忙')
    const [xuerongrong,setXuerongrong] = useState('雪容融在忙')
    return (
        <div>
            <button onClick={()=>{setXuerongrong(new Date().getTime())}}>雪容融</button>
            <button onClick={()=>{setBingdwendwen(new Date().getTime()+'冰墩墩向我们走来')}}>冰墩墩</button>
            <Child name={xuerongrong}>
                {bingdwendwen}
            </Child>
        </div>
    )
}
export default Example

子组件:按理说父组件重新渲染,子组件也应该重新渲染,如果子组件里面要做一个很大的计算,那么每次父组件更新组件,子组件就会重新计算,会有很大的一个性能问题。但是如果在子组件中使用了useMemo,那么就变成了父组件更新,而子组件不一定更新了,因为很有可能父组件重新渲染只是改变了自己的state,而子组件的state并没有改变,即只有当子组件中的useMemo依赖的参数变化时才会重新渲染子组件。

import {useMemo} from 'react'
const Child = ({name,children}) => {
    const changeXuerongrong = (name) => {
        console.log('雪容融向我们走来了')
        return name + ',雪容融向我们走来了'
    }
    // const actionXuerongrong = changeXuerongrong(name)
    const actionXuerongrong = useMemo(()=> changeXuerongrong(name),[name]) 
    //👇的写法,加一个大括号就不对了,因为箭头函数不加大括号会自动return,所以下面的例子加个return即可
    // const actionXuerongrong = useMemo(()=> {changeXuerongrong(name)},[name]) 
    return (
        <div>
            <div>{actionXuerongrong}</div>
            <div>{children}</div>
        </div>
    )
}
export default Child

4)React.Memo (React顶层api)

React.Memo为高阶组件

const MyComponent = React.memo(function MyComponent(props){
	//使用props渲染
})

如果你的组件在相同props的情况下渲染相同的结果,那么你可以通过将其包装在React.memo中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React将跳过渲染组件的操作并直接复用最近一次渲染的结果。
React.memo仅检查props变更。如果函数组件被React.memo包裹,且其现实中拥有useState,useReducer或者useContextHook,当statecontent发生变化时,它仍会重新渲染。
默认情况下只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

function MyComponent(props){
	//使用props渲染
}
function areEqual(prevProps, nextProps){
	//如果把nextProps传入render方法的返回结果与
	//将prevProps传入render方法的返回结果一致则返回true,
	//否则返回false
}
export default React.memo(MyComponent,areEqual);

此方法仅作性能优化的方式存在。

5)React.memo例子

父组件

import React ,{useState} from 'react'
import Child from './child'
const Example = () => {
    const [bingdwendwen,setBingdwendwen] = useState('冰墩墩在忙')
    const [xuerongrong,setXuerongrong] = useState('雪容融在忙')
    return (
        <div>
            <button onClick={()=>{setXuerongrong(new Date().getTime())}}>雪容融</button>
            <button onClick={()=>{setBingdwendwen(new Date().getTime()+'冰墩墩向我们走来')}}>冰墩墩</button>
            <Child name={xuerongrong}>
                {bingdwendwen}
            </Child>
        </div>
    )
}
export default Example

子组件

import {useMemo} from 'react'
const Child = ({name,children}) => {
    const changeXuerongrong = (name) => {
        console.log('雪容融向我们走来了')
        return name + ',雪容融向我们走来了'
    }
    const actionXuerongrong = changeXuerongrong(name)
    const actionXuerongrong = useMemo(()=> changeXuerongrong(name),[name]) 
    return (
        <div>
            <div>{actionXuerongrong}</div>
            <div>{children}</div>
        </div>
    )
}
export default React.memo(Child)

7.useCallback

1)useCallback的使用场景

  • 对于需要传递函数给子组件的场合,不使用useCallback的话,子组件每次都会重新渲染
  • 在调用节流,防抖函数时使用。

2)useCallback

const memoizedCallback = useCallback(
	() => {
		doSomething(a,b);
	},
	[a,b],
);

把内联回调函数及依赖项数组作为参数传入useCallback,它将返回该回调函数的memoized版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要的渲染的子组件时,它将非常有用。

3)useCallback的使用

useCallback可以理解为缓存一个函数。如果依赖没有变的话,就会一直是旧的依赖的函数。

8.useRef

  1. useRef获取React JSX中的DOM元素,获取后你就可以控制DOM的任何东西了。但是一般不建议这样来做,React界面的变化可以通过状态来控制。
  2. useRef来保存变量,这个在工作中也能很少用到,我们有了useContext这样的保存其实意义不大
import React,{useEffect, useRef,useState} from 'react'
const Example = () => {
    const inputE1 = useRef(null)
    const onButtonClick = () => {
        inputE1.current.value = "hello,bingdwendwen!"
        console.log(inputE1)
    }
    const [text,setText] = useState('bingdwendwen')
    const textRef = useRef()
    useEffect(()=>{
        textRef.current = text
        console.log('textRef.current:',textRef.current)
    })
    return (
        <div>
            <input ref={inputE1} type="text" />
            <button onClick={onButtonClick}>在input上展示文字</button>
            <br/>
            <input value={text} onChange={(e) => {setText(e.target.value)}}/>
        </div>
    )
}
export default Example

9.自定义Hooks函数

实例,自第一个实时监测浏览器窗口大小的hooks函数
自定义hooks函数,记住一定要用use开头

import React,{useState,useEffect,useCallback} from 'react'
//自定义hooks函数,必须以use开头
const useWinSize = () => {
    const [size,setSize] = useState({
        width:document.documentElement.clientWidth,
        height:document.documentElement.clientHeight,
    })
    const onResize = useCallback(() => {
        setSize({
            width:document.documentElement.clientWidth,
            height:document.documentElement.clientHeight,
        })
    })
    useEffect(() => {
        window.addEventListener('resize',onResize)
        return ()=>{
            window.removeEventListener('resize',onResize)
        }
    },[])
    return size
}

const Example = () => {
    const size = useWinSize()
    return (
        <div>页面Size:{size.width}x{size.height}</div>
    )
}

export default Example
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值