React Hooks

React Hooks

学习视频地址:👉哔哩哔哩技术胖Reactt Hooks免费视频教程

笔记参考博客地址:👉技术胖Reactt Hooks免费视频教程博客笔记

1.React Hooks介绍和开发环境搭建

1.介绍

React Hooks就是用函数的形式代替原来的继承类的形式,并且使用预函数的形式管理state,有Hooks可以不再使用的形式定义组件了。这时候你的认知也要发生变化了,原来把组件分为有状态组件和无状态组件有状态组件用类的形式声明,无状态组件用函数的形式声明。那现在所有的组件都可以用函数来声明了。

2.开发环境搭建

使用create-react-app创建项目,然后只留/src/index.js文件,重新修改里面的代码:

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(<App />, document.getElementById('root'));
3.简单示例对比

通过一个示例:点击按钮,屏幕上会显示你点击的次数来比较原来的写法和React Hooks写法的区别

原来的写法

import React, { Component } from 'react';

class Example extends Component {
    constructor(props) {
        super(props);
        this.state = { 
            count: 0
         }
         this.addcount = this.addcount.bind(this); //可能会出现this指向出错,需要重新绑定
    }
    render() { 
        return ( 
            <div>
                <p>You clicked {this.state.count} times</p>
                <button onClick={this.addcount}>Click me</button>
            </div>
         )
    }
    addcount (){
        this.setState({
           count: this.state.count + 1
        })
    }
}
 
export default Example;

React Hooks写法

import React, { useState } from 'react';

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

注意,React Hooks只能在React16.8及以上版本使用。用了React Hooks之后,所有的组件都可以用函数来声明

2.useState介绍和多状态声明

1.介绍

useState是react自带的一个hook函数,它的作用是用来声明状态变量

useState的三个用法,分别是声明、读取、使用(修改)

声明

const [ count , setCount ] = useState(0);

这种方法是ES6语法中的数组解构,这样看起来代码变的简单易懂。如果用正常写法,那么就需要三行:

let _useState = userState(0) //声明一个新数组_useState
let count = _useState[0]
let setCount = _useState[1]

声明代码的大致意思就是声明了一个初始值为0的变量count,并且声明了一个用来改变count值的方法函数setCount

读取

<p>You clicked {count} times</p>

读取是很简单的,只要使用{count}就可以,因为这时候的count就是JS里的一个变量,想在JSX中使用,值用加上{}就可以。

使用(修改)

<button onClick={()=>{setCount(count+1)}}>click me</button>

直接调用setCount函数,这个函数接收的参数是修改过的新状态值。接下来的事情就交给React,他会重新渲染组件。React自动帮助我们记忆了组件的上一次状态值。

2.多状态声明

比如现在我们要声明多个状态,有年龄(age)、性别(sex)和工作(work)。代码为:

import React, { useState } from 'react';
function Example2(){
    const [ age , setAge ] = useState(18)
    const [ sex , setSex ] = useState('男')
    const [ work , setWork ] = useState('前端程序员')
    return (
        <div>
            <p>JSPang 今年:{age}</p>
            <p>性别:{sex}</p>
            <p>工作是:{work}</p>

        </div>
    )
}
export default Example2;
import React, { useState } from 'react';

let showSex = true
function Example2(){
    const [ age , setAge ] = useState(18)
    if(showSex){
        const [ sex , setSex ] = useState('男')
        showSex=false
    }
...
}
export default Example2;

但是,不能像上面代码中一样把useState声明写在条件判断语句中,React是根据useState出现的顺序来确定的React Hooks不能出现在条件判断语句中,因为它必须有完全一样的渲染顺序

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

在用Class制作组件时,经常会用生命周期函数,来处理一些额外的事情(副作用:和函数业务主逻辑关联不大,特定时间或事件中执行的动作,比如Ajax请求后端数据,添加登录监听和取消登录,手动修改DOM等等)。在React Hooks中也需要这样类似的生命周期函数,比如在每次状态(State)更新时执行,它为我们准备了useEffect

在之前声明类的组件中:

import React, { Component } from 'react';

class Example3 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)}>Chlick me</button>
        </div>
    );
}

addCount(){
	this.setState({count:this.state.count+1})
}

}

export default Example3;

使用useEffect代替上面的两个声明周期函数改写后:

import React, { useState , useEffect } from 'react';

function Example(){
    const [ count,setCount ] = useState(0)

    useEffect(()=>{
        console.log(`useEffect=>You clicked ${count} times`)
    })

    return (
        <div>
            <p>React Hooks</p>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>Click me</button>
        </div>
        
    )
}
 
export default Example;

4.useEffect实现ComponentWillUnmount生命周期函数

在写React应用的时候,在组件中经常用到componentWillUnmount生命周期函数(组件将要被卸载时执行)。比如我们的定时器要清空,避免发生内存泄漏;比如登录状态要取消掉,避免下次进入信息出错。所以这个生命周期函数也是必不可少的。

1.useEffect解绑副作用

通过路由选择来使得组件销毁,首先进入项目根目录安装一下React路由:

npm install --save react-router-dom

然后在Example.js中引入并编写两个新组件Index和List

import { BrowserRouter as Router, Route, Link } from "react-router-dom"
...
function Index() {
    return <h2>JSPang.com</h2>;
}

function List() {
    return <h2>List-Page</h2>;
}
...
function Example(){
	return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>click me</button>

            <Router>
                <ul>
                    <li> <Link to="/">首页</Link> </li>
                    <li><Link to="/list/">列表</Link> </li>
                </ul>
                <Route path="/" exact component={Index} />
                <Route path="/list/" component={List} />
            </Router>
        </div>
	)
}

如果路由能够正常使用,再向两个新路由中添加useEffect

function Index() {
    useEffect(()=>{
        console.log('useEffect=>老弟,你来了!Index页面')
    })
    return <h2>JSPang.com</h2>;
}

function List() {
    useEffect(()=>{
        console.log('useEffect=>老弟,你来了!List页面')
    })
    return <h2>List-Page</h2>;
}

这样就能监听到页面的进入(初始渲染)了,另外,在useEffect中还可以通过return返回一个函数的形式进行解绑,也就是来监听页面的关闭(组件的销毁),实现了componentWillUnmount方法:

useEffect(()=>{
    console.log('useEffect=>老弟,你来了!Index页面')
    return ()=>{
    	console.log('老弟,你走了!Index页面')
    }
})

但是,这样做的话当你点击计数器的按钮的时候,也会打印输出这两句话。其实每次状态发生变化useEffect都进行了解绑。

2.useEffect的第二个参数

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

function Index() {
    useEffect(()=>{
        console.log('useEffect=>老弟你来了!Index页面')
        return ()=>{
            console.log('老弟,你走了!Index页面')
        }
    },[])
    return <h2>JSPang.com</h2>;
}

如果给计数器也加上第二个参数为空数组

const [ count , setCount ] = useState(0);

useEffect(()=>{
	console.log(`useEffect=>You clicked ${count} times`)
    return ()=>{
    	console.log('====================')
    }
},[])

注意,这时候的代码是不能执行解绑副作用函数的(因为计数器对应的组件始终都在页面上没有被销毁)。但是如果我们想每次count发生变化,我们都进行解绑,只需要在第二个参数的数组里加入count变量就可以了。如下:

const [ count , setCount ] = useState(0);

useEffect(()=>{
	console.log(`useEffect=>You clicked ${count} times`)
    return ()=>{
    	console.log('====================')
    }
},[])

5.useContext父子组件传值

在用类声明组件时,父子组件的传值是通过组件属性props进行的,那现在使用方法(Function)来声明组件,已经没有了constructor构造函数也就没有了props的接收。useContext,它可以帮助我们跨越组件层级直接传递变量,实现共享。需要注意的是useContextredux的作用是不同的,一个解决的是组件之间值传递的问题,一个是应用中统一管理状态的问题,但通过和useReducer的配合使用,可以实现类似Redux的作用。

Context的作用就是对它所包含的组件树提供全局共享数据的一种技术。

1.createContext创建Context

新建Example4.js,写入计数器的代码:

import React, { useState , useEffect } from 'react';

function Example4(){
    const [ count , setCount ] = useState(0);
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>click me</button>
        </div>
    )
}
export default Example4;

然后引入createContext方法,并通过这个方法创建出一个共享的"组件"(容器):

import React, { useState , createContext } from 'react';

const CountContext = createContext();

接着在return中写入该组件:

<CountContext.Provider value={count}> //value后的值就是要传递给子组件的值

</CountContext.Provider>
2.useContext接收Context变量

然后声明一个子组件Counter

import React, { useState , createContext , useContext } from 'react';

function Counter() {
    let count = useContext(CountContext)
    return (
        <h2>{count}</h2>
    )
}

最后一步将子组件渲染到页面上去,注意,子组件标签要写在<CountContext.Provider value={count}></CountContext.Provider>闭合标签内,如下:

<CountContext.Provider value={count}>
    <Counter />
</CountContext.Provider>

6.useReducer介绍和简单使用

useReduceruseContext很像,并且合作可以完成类似的Redux库的操作。在开发中使用useReducer可以让代码具有更好的可读性和可维护性,并且会给测试提供方便。

reducer其实就是一个函数,这个函数接收两个参数,一个是状态(变量),一个用来控制业务逻辑的判断参数,例如:

function countReducer(state, action) {
    switch(action.type) {
        case 'add':
            return state + 1;
        case 'sub':
            return state - 1;
        default: 
            return state;
    }
}

新建一个Example5.js的文件,然后用useReducer实现计数器的加减双向操作

import React, { useReducer } from 'react';

function ReducerDemo(){
    const [ count , dispatch ] =useReducer((state,action)=>{
        switch(action){
            case 'add':
                return state+1
            case 'sub':
                return state-1
            default:
                return state
        }
    },0)  //0是count的初始默认值
    return (
       <div>
           <h2>现在的分数是{count}</h2>
           <button onClick={()=>dispatch('add')}>Increment</button>
           <button onClick={()=>dispatch('sub')}>Decrement</button>
       </div>
    )

}

export default ReducerDemo

7.useReducer代替Redux-1

使用useContextuseReducer是可以实现类似Redux的效果,并且一些简单的个人项目,完全可以用下面的方案代替Redux,这种做法要比Redux简单一些。

useContext:可访问全局状态,避免一层层的传递状态。这符合Redux其中的一项规则,就是状态全局化,并能统一管理。

useReducer:通过action的传递,更新复杂逻辑的状态,主要是可以实现类似Redux中的Reducer部分,实现业务逻辑的可行性。

通过点击不同按钮来实现样式切换的示例:

1.设计界面

首先新建一个文件夹example6,在里面新建ShowArea.jsButon.js,分别作为显示组件和按钮切换组件:

ShowArea.js

import React from 'react';

function ShowArea(){
    return (
        <div style={{color:'blue'}}>字体颜色为blue</div>
    )
}
export default ShowArea

Buton.js

import React from 'react';

function Buttons(){
    return (
        <div>
            <button>红色</button>
            <button>黄色</button>
        </div>
    )
}

export default Buttons

然后新建一个Example6.js来讲两个组件包裹起来:

import React, { useReducer } from 'react';
import ShowArea from './ShowArea';
import Buttons from './Buttons';

function Example6(){
    return (
        <div>
                <ShowArea />
                <Buttons />
        </div>
    )
}

export default Example6
2.逻辑实现

首先利用useContext来实现状态共享:新建一个color.js

import React, { createContext } from 'react';

export const ColorContext = createContext({})

export const Color = props=>{
    return (
        <ColorContext.Provider value={{color:"blue"}}>
            {props.children} //表示将color的值传递给Color的子组件
        </ColorContext.Provider>
    )
}

接着在Example6.js中引入Color组件并将之前的两个组件包裹起来:

import React from 'react';
import ShowArea from './ShowArea';
import Button from './Button';
import { Color } from './color';

function Example6(){
    return (
        <div>
                <Color>
                    <ShowArea />
                    <Button />
                </Color>
        </div>
    )
}

export default Example6

然后再改写showArea.js文件,我们会引入useContext和在color.js中声明的ColorContext,让组件可以接收全局变量:

import React , { useContext } from 'react';
import { ColorContext } from './color';

function ShowArea(){
    const {color} = useContext(ColorContext)
    return (<div style={{color:color}}>字体颜色为{color}</div>)
}

export default ShowArea

这样就实现了在Color组件中将颜色值传递给ShowArea组件。

8.useReducer代替Redux-2

1.在Color.js中添加Reducer

关于颜色值改变的逻辑都在Color.js中,在文件里添加一个reducer,用于处理颜色更新的逻辑。

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>
    )
}
2.通过dispatch修改状态

buttons.js使用dispatch来完成按钮的相应操作了。先引入useContextColorContextUPDATE_COLOR,然后写onClick事件就可以了:

import React ,{useContext} from 'react';
import {ColorContext,UPDATE_COLOR} from './color'

function Button(){
    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 Button

9.useMemo优化React Hooks程序性能

useMemo主要用来解决使用React hooks产生的无用渲染的性能问题。使用function的形式来声明组件,失去了shouldCompnentUpdate(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分mountupdate两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。useMemouseCallback都是解决上述性能问题的。

首先新建一个Example7.js

import React , { useState } from 'react';

function Example7(){
    const [xiaohong , setXiaohong] = useState('小红在等待')
    const [xiaoming , setXiaoming] = useState('小明在等待')
    return (
        <div>
            {/* 点击事件 */}
            <button onClick={()=>{setXiaohong(new Date().getTime()+',小红向我们走来了')}}>小红</button>
            <button onClick={()=>{setXiaoming(new Date().getTime()+',小明向我们走来了')}}>小明</button>
            <ChildComponent name={xiaohong}>{xiaoming}</ChildComponent>
        </div>
    )
}

function ChildComponent({name,children}){
    // name = 小红,children = 小明在等待
    function changeXiaohong(name){
        console.log('她来了,她来了。小红向我们走来了')
        return name+',小红向我们走来了' //小明在等待,,小红向我们走来了
    }

    const actionXiaohong = changeXiaohong(name)
    return (
        <>
            <div>{actionXiaohong}</div> 
            {/* {actionXiaohong}  =  小明在等待,,小红向我们走来了 */}
            <div>{children}</div>
            {/*{children} =  小明在等待 */}
        </>
    )
}

export default Example7

这段代码的主要功能就是点击人物按钮时会触发事件,显示时间戳和当前点击的人物。但是当点击小明时,关于小红的方法函数changeXiaohong也会被执行,也就是当父组件状态发生改变时,子组件都会刷新。这样就会造成性能的极大浪费。我们要优化的就是当我们点击小明按钮时,小红对应的changeXiaohong方法不能执行,只有在点击小红按钮时才能执行

使用useMemo,然后给她传递第二个参数,参数匹配成功,才会执行

// 之前的写法 const actionXiaohong = changeXiaohong(name)
//下面这句话的意思就是当name发生改变(在源程序中name指的就是小红)时才会触发changeXiaohong()这个方法
const actionXiaohong = useMemo(()=>changeXiaohong(name),[name])

10.useRef获取DOM元素和保存变量

useRef的作用:

  • useRef获取React JSX中的DOM元素,获取后你就可以控制DOM的任何东西了。但是一般不建议这样来作,React界面的变化可以通过状态来控制。
  • useRef来保存变量,这个在工作中也很少能用到,因为已经有了useContext,这样的保存其实意义不大。
1.useRef获取DOM元素

界面上有一个文本框,在文本框的旁边有一个按钮,当我们点击按钮时,在控制台打印出input的DOM元素,并进行复制到DOM中的value上。这一切都是通过useRef来实现:

import React, { useRef } from 'react';

function Example8() {
    const inputEl = useRef(null)
    const onButtonClick=()=>{ 
        inputEl.current.value="Hello , World"
        console.log(inputEl) //输出获取到的DOM节点
    }
    return (
        <>
            {/*保存input的ref到inputEl */}
            <input ref={inputEl} type="text"/>
            <button onClick = {onButtonClick}>在input上展示文字</button>
        </>
    )
}
export default Example8
2.useRef保存普通变量

useRef可以保存React中的变量。先用useState声明了一个text状态和setText函数。然后编写界面,界面就是一个文本框。然后输入的时候text的值不断变化,并在每次变化之后都将变量保存在useRef中:

import React, { useRef , useState , useEffect} from 'react';

function Example8() {
    const inputEl = useRef(null)
    const onButtonClick=()=>{ 
        inputEl.current.value="Hello , World"
        console.log(inputEl) //输出获取到的DOM节点
    }
    const [text, setText] = useState('jspang')
    const textRef = useRef()

    useEffect(()=>{
        textRef.current = text; //在每次变量变化后都将变量保存
        console.log('textRef.current:', textRef.current)
    })
    return (
        <>
            {/*保存input的ref到inputEl */}
            <input ref={inputEl} type="text"/>
            <button onClick = {onButtonClick}>在input上展示文字</button>
            <br/>
            <br/>
            <input value={text} onChange={(e)=>{setText(e.target.value)}} />
        </>
    )
}
export default Example8

11.自定义Hooks函数获取窗口值大小

自定义Hooks函数和用Hooks创建组件很相似,跟我们平时用JavaScript写函数几乎一模一样,可能就是多了些React Hooks的特性,自定义Hooks函数偏向于功能,而组件偏向于界面和业务逻辑。

1.编写自定义函数

封装成一个自定义Hooks函数,记住一定要用use开头,这样才能区分出什么是组件,什么是自定义函数。

新建一个文件Example9.js,然后编写一个useWinSize自定义函数,编写时我们会用到useStateuseEffectuseCallback所以要用import进行引入。

import React, { useState ,useEffect ,useCallback } from 'react';

然后编写函数,函数中先用useState设置size状态,然后编写一个每次修改状态的方法onResize,这个方法使用useCallback,目的是为了缓存方法(useMemo是为了缓存变量)。 然后在第一次进入方法时用useEffect来注册resize监听时间。为了防止一直监听所以在方法移除时,使用return的方式移除监听。最后返回size变量就可以了。

function 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;

}
2.使用自定义函数
function Example9(){

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

export default Example9 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

偶尔躲躲乌云_0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值