React之Hooks---(参考jsPang)

前言:

  

  React Hooks是React生态圈近期里边最火的新特性之一,FaceBook于2018年底推出。

  它改变了原始的React类的开发方式,改用了函数形式;

  它改变了复杂的状态操作形式,让程序员用起来更轻松;

  它改变了一个状态组件的复用性,让组件的复用性大大增加

  

大纲:

01_ReactHooks开发环境搭建和HelloWorld编写
02_useState的介绍和多状态声明
03_useEffect代替生命周期函数
04_useEffect实现ComponentWillUnmont生命周期函数
05_useContext父子组件传值React Hook 中 createContext & useContext 跨组件透传上下文与性能优化
06_useReducer介绍和简单使用
07_useReducer代替Redux小案例-1
08_useReducer代替Redux小案例-2
09_useMemo解决子组件重复执行问题
10_useRef获取DOM和保存变量
11_自定义Hooks函数

 

 

(1)01_ReactHooks开发环境搭建和HelloWorld编写

  首先用脚手架创建项目

>create-react-app hooks-test

  然后精简化代码,将其他没用的文件删除,只留下index.js文件

  接下来做个案例,如下所示,每点击一次按钮,次数+1,分别用之前的类继承开发方式和Hooks开发方式做下该案例,以此做下对比

  

  首先使用之前的Class继承方式编写,如下所示

  

 

  此时便可以实现点击按钮类增1

  接下来使用Hooks写法编写该案例,注意版本

注意:Hooks是新版react内置特性(V16.8以上才可以使用),所以无需下载其他依赖.

  

  

  此时便用Hooks形式实现了该案例

  

  

 

(2)02_useState的介绍和多状态声明

  以之前案例为依据,如下所示

  

  本节将从3个方面介绍useState:声明、读取、修改(使用进行编辑)。

  1、声明

const [count,addCountFn] = useState(0)

  useState为一个方法,方法里接受一个初始值0

  左侧写法为Es6的数组解构语法,如果不用解构赋值,则ES5语法如下

  

 

  数组[]里一共有两个值,第一个为初始值,第二个为方法,用来改变初始值。

  2、读取

  初始值解构出来后可以看作一个变量,可以直接在后面使用,如下所示

  

  3、修改

  SetCount方法用于修改初始值,使用时括号里直接写修改后的值即可。

  思考:至此可以实现count数量的修改记录,但是代码里看不出来在哪里做了记录?

答案:通过useState的顺序记住的

  接下来通过多状态声明做下介绍

  

  

  此时便声明了多个状态并进行了对应展示,之前也介绍过,useState是通过顺序来记录的,接下来我们做下改动,如下所示

  

  由此可以判断出useState里必须以顺序进行调用,且不支持条件判断,因为useState是按照顺序进行多状态的记录。此外,还可以添加事件处理,如下所示

  

  

 

 

   

(3)03_useEffect代替生命周期函数

  首先使用之前的Class方式做下生命周期钩子的验证,如下所示

  

  

  接下来使用useEffect替代生命周期钩子

  

 

  测试如下

 

  

  我们会发现初始化时便触发了useEffect,输出点击次数为0。接着进行点击,会依次打印,如下所示

  

注意:
 1、eact-hooks的useEffect一个方法代替了之前Class写法的两个钩子函数,即componentDidMount和componentDidUpdate
  两个生命周期函数钩子
2、useEffect方法是异步的,会进行延时处理。但也有坏处,无法实时处理某些场景,如浏览器窗口缩放时,实时计算窗口大小,进行重新布局。

  

 

 

(4)04_useEffect实现ComponentWillUnmount生命周期函数

  场景:某个组件里绑定了定时器,当组件销毁时需要对定时器进行解绑,否则影响性能,容易造成内存溢出。

  所以本节主要使用useEffect实现componentWillUnmount进行组件销毁前的相关监听操作。接下来首先引入路由,进行路由相关操作,才可以实现组件的销毁过程.

  1、下载引入路由react-router-dom

>npm i react-router-dom --save

  

 

 

  2、编写两个简单的无状态组件用于换页

  因为hooks下组件可以用方法代替

  

 

 

  接下来进行路由配置

  

 

 

  此时便可以进行切换,如下所示

  

 

 

  接着给两个页面方法添加useEffect进行监听

  

 

 

  测试如下

  

 

 

  此时来回切换页面时便会触发对应的useEffect方法

  在组件解绑时,react-hooks设计了对应的方法用于监听组件解绑。在useEffect方法里添加return返回匿名函数,从而监听组件解绑,如下所示

  

 

 

  测试如下

  

 

 

  我们发现,此时在离开组件时(组件状态更改)便会进行解绑监听。但此时还存在问题,即只要组件状态更改,就会触发该方法的解绑监听,如下所示,我们点击按钮,实现累加效果,会发现每次点击按钮进行累加,状态更改时,都会触发解绑监听.

  

 

 

  此时便需要用到useEffect的第二个参数(数组格式),如下所述

  

useEffect语法:
useEffect(()
=>{ ... ... },[[参数1],[参数2]...[参数n]])
分析:
参数1为箭头函数
    参数2为数组,里面可以传入参数,也可以不传。如果不传参,则代表整个组件发生改变时才触发该return方法;如果传入参数,则代表除了组件销毁外,
   当传入的参数发生改动时也可以触发该return方法。
所以,如果我们想监听组件的销毁前的钩子监听,则使用空数组[],不对其进行传参

  此时便可以实现组件的销毁前钩子监听,而不受组件内部状态更改的影响,测试如下

  

 

 

  接着我们对数组进行传参,然后绑定事件进行更改,看看是否如我们所想进行触发

  

  

  此时我们发现,因为传入了content参数,所以当content改动和组件卸载时都会触发return方法。

  

 

 

(5)05_useContext父子组件传值(React Hook 中 createContext & useContext 跨组件透传上下文与性能优化

有了useState和useEffect已经可以实现大部分的业务逻辑了,但是React Hooks中还是有很多好用的Hooks函数的,比如useContext和useReducer。

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

  之前的Class形式创建组件里,组件间父子组件传值时使用this.props,无状态组件主要使用props进行传值。

  在Hooks形式,即function创建的组件里,父子组件传值主要通过useContext.

  一般useContext会和useReducer配合使用,useContext主要解决父子组件传值,useReducer主要解决状态共享,实现redux的功能。接下来看下使用步骤

   1、先将需要传递的值暴露到上下文context,即父组件通过createContext 函数创建context上下文

  引入创建上下文方法createContext,然后创建传递值上下文.此时就可进行共享了,CountContext相当于一个组件

  

  2、父组件里,共享传值上下文

  

这段代码就相当于把count变量允许跨层级实现传递和使用了(也就是实现了上下文),当父组件的count变量发生变化时,子组件也会发生变化。
接下来我们就看看一个React Hooks的组件如何接收到这个变量。

  3、子组件使用useContext接收上下文变量

  

 

  因为这里要使用createContext创建的上下文,所以需要在父组件所在组件里导出

  

  接下来在子组件导入即可

  

 

  小结:

  

  以上为讲解时的案例。下面介绍下开发时的使用方法,详见createContext 和 useContext 结合使用实现方法共享.

  这里对上述案例做下修改

  1、App.js

import React,{useState,createContext} from 'react'
/** 1. 引入 createContext 创建的上下文 */
import {CountContext} from './context-manager'

import Child from './Child.jsx'

/** 2. 创建 Provider */
function App(){
    const [count,setCount] = useState(0)
    return (
        <div>
            <p>You Click {count} times.</p>
            <button onClick={()=>{setCount(count+1)}}>Click me(父)</button>
            <CountContext.Provider value={{setCount}}>
                <Child count={count}></Child>
            </CountContext.Provider>
        </div>
    )
}
export default App

  2、Child.jsx

import React, { useContext ,Fragment} from 'react';
import {CountContext} from './context-manager'
function Child(props){
    let {setCount} = useContext(CountContext)
    return (
        <Fragment>
            <h3>我是子组件,接收父组件的值为:{props.count}</h3>
            <button onClick={() => { setCount(props.count+ 1) }}>Clike me(子)</button>
        </Fragment>
    )
}

export default Child

  3、context-manager.js

import React from 'react';

/* 使用 createContext 创建上下文 */
export const CountContext = React.createContext(null);
/** 或者 export const CountContext = new createContext() */

  效果如下

  

 

  此时子组件可以直接使用上下文中的方法,修改父组件的state.

可以发现,在子组件中点击按钮,直接调用 Context 透传过来的方法,可以修改父组件的 state,子组件则会重新渲染。

这种方式显式的避免了多级 props 的层层透传问题,虽然 Demo 只有一级 子组件,即使存在多级子组件也可以直接修改

 

 

(6)06_useReducer介绍和简单使用

  

 

  这里注意:switch...case里return后不需要break,因为return便会结束函数体,不会造成函数穿透。

  useReducer为Hooks新增的方法,增强完善了reducer操作

  使用步骤如下

  1、引入useReducer方法

  

 

  2、编写组件---定义reducer

  

  useReducer方法接收两个参数,第一个为箭头函数(内部处理action),第二个为初始值。前面的数组[]里传入两个参数,第一个为箭头函数内部action的返回结果,第二个为派发器dispatch.接下来调用

  

  此时测试发现按钮无效,原因在于switch判断时为对象格式action.type,而我们派发时是字符串

  

 

  完整代码如下

import React, { useReducer } from 'react'

function ReducerDemo(){
    const [count,dispatch] = useReducer((state,action)=>{
        switch(action){
            case 'add':
                return state+1
            case 'cut':
                return state-1
            default:
                return state
        }
    },0)
    return (
        <div>
            <h2>现在的分数为{count}</h2>
            <button onClick={()=>dispatch('add')}>add</button>
            <button onClick={()=>dispatch('cut')}>cut</button>
        </div>
    )
}

export default ReducerDemo

 

  

 

 

(7)07_useReducer代替Redux小案例-1

  本节利用useReducer和useContext实现一个redux效果(状态管理useReducer和状态共享useContext

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

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

  由上可知useContext共享状态+useReducer管理状态即可实现redux效果。

  1、展示区域

  

 

  2、按钮组件

  

 

  3、将之前组件整合

  

 

  

 

  4、引入整合文件并展示

  

 

  这里已经展示出基本结构,接下来首先做下状态共享useContext.

  

 

  

 

  由此便将color共享了出去,变成了上下文。接下来进行使用

  

  最后动态接受上下文,并进行显示

  

 

  此时便可以实现动态接收

  

 

  也可以在color.js进行修改

  

 

  

 

  由此判断,此时状态已经共享出去,接下来使用useReducer进行业务逻辑编写,两者结合便是redux。

  其他参考文章:

  编写基本UI组件

  既然是一个实例,就需要有些界面的东西,小伙伴们不要觉的烦。在/src目录下新建一个文件夹Example6,有了文件夹后,在文件夹下面建立一个showArea.js文件。代码如下:

1
2
3
4
5
6
7
import React from 'react';
function ShowArea(){
    
    return (<div style={{color:'blue'}}>字体颜色为blue</div>)
 
}
export default ShowArea

  显示区域写完后,新建一个Buttons.js文件,用来编写按钮,这个是两个按钮,一个红色一个黄色。先不写其他任何业务逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';
 
function Buttons(){
    return (
        <div>
            <button>红色</button>
            <button>黄色</button>
        </div>
    )
}
 
export default Buttons

  然后再编写一个组合他们的Example6.js组件,引入两个新编写的组件ShowAreaButtons,并用<div>标签给包裹起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { useReducer } from 'react';
import ShowArea from './ShowArea';
import Buttons from './Buttons';
 
 
function Example6(){
    return (
        <div>
                <ShowArea />
                <Buttons />
        </div>
    )
}
 
export default Example6

  这步做完,需要到/src目录下的index.js中引入一下Example6.js文件,引入后React才能正确渲染出刚写的UI组件。

1
2
3
4
5
import React from 'react';
import ReactDOM from 'react-dom';
import Example from './Example6/Example6'
 
ReactDOM.render(<Example />, document.getElementById('root'));

  做完这步可以简单的预览一下UI效果,虽然很丑,但是只要能满足学习需求就可以了。我们虽然都是前端,但是在学习时没必要追求漂亮的页面,关键时把知识点弄明白。我们写这么多文件,也就是要为接下来的知识点服务,其实这些组件都是陪衬罢了。

  编写颜色共享组件color.js

  有了UI组件后,就可以写一些业务逻辑了,这节课我们先实现状态共享,这个就是利用useContext。建立一个color.js文件,然后写入下面的代码。

1
2
3
4
5
6
7
8
9
10
11
import React, { createContext } from 'react';
 
export const ColorContext = createContext({})
 
export const Color = props=>{
    return (
        <ColorContext.Provider value={{color:"blue"}}>
            {props.children}
        </ColorContext.Provider>
    )
}

  代码中引入了createContext用来创建共享上下文ColorContext组件,然后我们要用{props.children}来显示对应的子组件。详细解释我在视频中讲解吧。

  有了这个组件后,我们就可以把Example6.js进行改写,让她可以共享状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useReducer } from 'react';
import ShowArea from './ShowArea';
import Buttons from './Buttons';
import { Color } from './color';   //引入Color组件
 
function Example6(){
    return (
        <div>
            <Color>
                <ShowArea />
                <Buttons />
            </Color>
             
        </div>
    )
}
 
export default Example6

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

1
2
3
4
5
6
7
8
9
10
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

  这时候就通过useContext实现了状态的共享,可以到浏览器中看一下效果。然后我们下节课再实现复杂逻辑状态的变化。

  

 

(8)08_useReducer代替Redux小案例-2

  在color.js中添加Reducer

  颜色(state)管理的逻辑代码我们都放在了color.js中,所以在文件里添加一个reducer,用于处理颜色更新的逻辑。先声明一个reducer的函数,它就是JavaScript中的普通函数,在讲useReducer的时候已经详细讲过了。有了reducer后,在Color组件里使用useReducer,这样Color组件就有了那个共享状态和处理业务逻辑的能力,跟以前使用的Redux几乎一样了。之后修改一下共享状态。我们来看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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>
    )
}

  注意,这时候我们共享出去的状态变成了color和dispatch,如果不共享出去dispatch,你是没办法完成按钮的相应事件的。

  通过dispatch修改状态

  目前程序已经有了处理共享状态的业务逻辑能力,接下来就可以在buttons.js使用dispatch来完成按钮的相应操作了。先引入useContextColorContextUPDATE_COLOR,然后写onClick事件就可以了。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React ,{useContext} from 'react';
import {ColorContext,UPDATE_COLOR} from './color'
 
function 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

  这样代码就编写完成了,用useContextuseReducer实现了Redux的效果,这个代码编写过程比Redux要简单,但是也是有一定难度的。希望第一次接触的小伙伴能自己动手写5遍以上,把这种模式掌握好。

 

(9)09_useMemo解决子组件重复执行问题

  useMemo优化React Hooks程序性能,解决子组件重复执行问题,详见useMemo优化React Hooks程序性能,解决子组件重复执行问题.

    useCallback优化React Hooks程序性能,详见useCallback优化React Hooks程序性能 .

 

(10)10_useRef获取DOM和保存变量

  useRef在工作中虽然用的不多,但是也不能缺少。它有两个主要的作用:

  • useRef获取React JSX中的DOM元素,获取后你就可以控制DOM的任何东西了。但是一般不建议这样来作,React界面的变化可以通过状态来控制。

  • useRef来保存变量,这个在工作中也很少能用到,我们有了useContext这样的保存其实意义不大,但是这是学习,也要把这个特性讲一下。

  1、useRef获取DOM元素

  

 

 

  

 

  此时点击按钮时,输入框的value值便发生改动。

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

  完整代码如下

import React, { useRef} from 'react';
function Example8(){
    const inputEl = useRef(null)
    const onButtonClick=()=>{
        inputEl.current.value="Hello ,JSPang"
        console.log(inputEl) //输出获取到的DOM节点
    }
    return (
        <>
            {/*保存input的ref到inputEl */}
            <input ref={inputEl} type="text"/>
            <button onClick = {onButtonClick}>在input上展示文字</button>
        </>
    )
}
export default Example8

  当点击按钮时,你可以看到在浏览器中的控制台完整的打印出了DOM的所有东西,并且界面上的<input/>框的value值也输出了我们写好的Hello ,JSPang。这一切说明我们可以使用useRef获取DOM元素,并且可以通过useRefu控制DOM的属性和值。

  2、useRef保存普通变量

  这个操作在实际开发中用的并不多,但我们还是要讲解一下。就是useRef可以保存React中的变量。我们这里就写一个文本框,文本框用来改变text状态。又用useReftext状态进行保存,最后打印在控制台上。写这段代码你会觉的很绕,其实显示开发中没必要这样写,用一个state状态就可以搞定,这里只是为了展示知识点。

  接着上面的代码来写,就没必要重新写一个文件了。先用useState声明了一个text状态和setText函数。然后编写界面,界面就是一个文本框。然后输入的时候不断变化。

import React, { useRef ,useState,useEffect } from 'react';
 
function Example8(){
    const inputEl = useRef(null)
    const onButtonClick=()=>{
        inputEl.current.value="Hello ,useRef"
        console.log(inputEl)
    }
    const [text, setText] = useState('jspang')
    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

  这时想每次text发生状态改变,保存到一个变量中或者说是useRef中,这时候就可以使用useRef了。先声明一个textRef变量,他其实就是useRef函数。然后使用useEffect函数实现每次状态变化都进行变量修改,并打印。最后的全部代码如下。

import React, { useRef ,useState,useEffect } from 'react';
function Example8(){
    const inputEl = useRef(null)
    const onButtonClick=()=>{
        inputEl.current.value="Hello ,useRef"
        console.log(inputEl)
    }
    //-----------关键代码--------start
    const [text, setText] = useState('jspang')
    const textRef = useRef()
 
    useEffect(()=>{
        textRef.current = text;
        console.log('textRef.current:', textRef.current)
    })
    //----------关键代码--------------end
    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

  这时候就可以实现每次状态修改,同时保存到useRef中了。也就是我们说的保存变量的功能,这边好似useRef的主要功能即获得DOM和变量保存

  

 

 

 

(11)11_自定义Hooks函数

   详见自定义Hooks函数(案例:获取窗口大小).

 

 

 

 

.

useRef获取DOM元素

posted @ 2020-03-17 11:01  剑仙6  阅读(568)  评论(0编辑  收藏  举报
欢迎访问个人网站www.qingchun.在线