ReactJS语法-React Hooks
简介
- Hook 是 React 16.8 的新增特性,它可在不编写class的情况下使用state以及其他的React特性(比如生命周期)。
- class组件相对于函数式组件优势
- class组件可以定义自己的state,用来保存组件自己内部的状态;函数式组件不可以,因为函数每次调用都会产生新的临时变量;
- class组件有自己的生命周期,可以在对应的生命周期中完成自己的逻辑;函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求;
- class组件可以在状态改变时只会重新执行render函数以及希望重新调用的生命周期函数componentDidUpdate等;函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次
- Class组件存在的问题
- 复杂组件变得难以理解:随着业务的增多,class组件会变得越来越复杂;因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度;
- 组件复用状态很难:一些状态的复用需要通过高阶组件,的redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用;
Hook作用
- 它可以让我们在不编写class的情况下使用state以及其他的React特性;
- Hook的出现基本可以代替之前所有使用class组件的地方;
- 但是如果是一个旧的项目,并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,可以渐进式的来使用它;
- Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用;
案例
-
和函数式组件结合hooks实现计数器案例
//CounterHook.jsx import { memo, useState } from "react"; // 普通的函数, 里面不能使用hooks // function foo() { // 不能使用hooks // const [ message ] = useState("Hello World") // return message // } // 在自定义的hooks中, 可以使用react提供的其他hooks: 必须使用use开头 // function useFoo() { // const [ message ] = useState("Hello World") // return message // } function CounterHook(props) { //只能在函数组件最外层调用 Hook const [counter, setCounter] = useState(0) //普通函数 // const message = foo() //自定义hooks // const message = useFoo() return ( <div> <h2>当前计数: {counter}</h2> <button onClick={e => setCounter(counter+1)}>+1</button> <button onClick={e => setCounter(counter-1)}>-1</button> </div> ) } export default memo(CounterHook) - useState来自react,需要从react中导入,它是一个hook;
- 参数:初始化值,如果不设置为undefined
- 返回值:数组,包含两个元素,
- 元素一:当前状态的值(第一调用为初始化值);
- 元素二:设置状态值的函数;
- 点击button按钮后,会完成两件事情
- 调用setCount,设置一个新的值;
- 组件重新渲染,并且根据新的值返回DOM结构;
- 使用两个额外的规则:
- 只能在函数组件最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用
useState
- State Hook的API就是 useState
- useState会帮助我们定义一个 state变量,useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。
- 一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。
- useState接受唯一一个参数,在第一次组件被调用时使用来作为初始化值。(如果没有传递参数,那么初始化值为undefined)。
- useState的返回值是一个数组,可以通过数组的解构,来完成赋值会非常方便。
useEffect
- Effect Hook 可以完成一些类似于class中生命周期的功能;
- 事实上,类似于网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(Side Effects)
- 所以对于完成这些功能的Hook被称之为 Effect Hook;
- useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数;
- 默认情况下,无论是第一次渲染之后,还是每次更新之后,都会执行这个 回调函数;
清除Effect
- 在class组件的编写过程中,某些副作用的代码,需要在componentWillUnmount中进行清除:
- useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B:这个函数B就是清楚函数
- 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数;
- 如此可以将添加和移除订阅的逻辑放在一起;它们都属于 effect 的一部分;
- React 何时清除 effect?
- React 会在组件更新和卸载的时候执行清除操作
举例:
import React, { memo } from 'react'
import { useState, useEffect } from 'react'
const App = memo(() => {
const [count, setCount] = useState(200)
// 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码
useEffect(() => {
// 当前传入的回调函数会在组件被渲染完成后, 自动执行
// 网络请求/DOM操作(修改标题)/事件监听
document.title = count
console.log("监听redux中数据变化, 监听eventBus中的why事件")
// 返回值: 回调函数 => 组件被重新渲染或者组件卸载的时候执行
return () => {
console.log("取消监听redux中数据变化, 取消监听eventBus中的why事件")
}
})
return (
<div>
<h2>当前计数: {count}</h2>
<button onClick={e => setCount(count+1)}>+1</button>
</div>
)
})
export default App
使用多个Effect
- 使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题:
- 比如网络请求、事件监听、手动修改DOM,这些往往都会放在componentDidMount中;
- 使用Effect Hook,我们可以将它们分离到不同的useEffect中:
- React 将按照 effect 声明的顺序依次调用组件中的每一个 effect;
import React, { memo, useEffect } from 'react'
import { useState } from 'react'
const App = memo(() => {
const [count, setCount] = useState(0)
// 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码
useEffect(() => {
// 1.修改document的title(1行)
console.log("修改title")
})
// 一个函数式组件中, 可以存在多个useEffect
useEffect(() => {
// 2.对redux中数据变化监听(10行)
console.log("监听redux中的数据")
return () => {
// 取消redux中数据的监听
}
})
useEffect(() => {
// 3.监听eventBus中的xxx事件(15行)
console.log("监听eventBus的xx事件")
return () => {
// 取消eventBus中的why事件监听
}
})
return (
<div>
<button onClick={e => setCount(count+1)}>+1({count})</button>
</div>
)
})
export default App
Effect性能优化
- 默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问
- 某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网络请求、订阅和取消订阅);
- 多次执行也会导致一定的性能问题;
- 如何决定useEffect在什么时候应该执行和什么时候不应该执行呢?useEffect实际上有两个参数:
- 参数一:执行的回调函数;
- 参数二:该useEffect在哪些state发生变化时,才重新执行;(受谁的影响)
- 如果一个函数不希望依赖任何的内容时(即整个组件生命周期只执行一次),也可以传入一个空的数组 []:
- 那么这里的两个回调函数分别对应的就是componentDidMount和componentWillUnmount生命周期函数了;
-
举例:
import React, { memo, useEffect } from 'react' import { useState } from 'react' const App = memo(() => { const [count, setCount] = useState(0) const [message, setMessage] = useState("Hello World") // [count]只有cont发生改变才会执行,message改变,不会受影响 useEffect(() => { console.log("修改title:", count) }, [count]) // []不依赖任何state元素,只会执行一次 useEffect(() => { console.log("监听redux中的数据") return () => {} }, []) useEffect(() => { console.log("监听eventBus的why事件") return () => {} }, []) useEffect(() => { console.log("发送网络请求, 从服务器获取数据") return () => { console.log("会在组件被卸载时, 才会执行一次") } }, []) return ( <div> <button onClick={e => setCount(count+1)}>+1({count})</button> <button onClick={e => setMessage("你好啊")}>修改message({message})</button> </div> ) }) export default App
useRef
- useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变。
- 最常用的ref是用来获取react中的dom
import React, { memo, useRef } from 'react'
const App = memo(() => {
const titleRef = useRef()
const inputRef = useRef()
function showTitleDom() {
//获取到h2的dom
console.log(titleRef.current)
//获取到input的dom,点击按钮直接将input聚焦
inputRef.current.focus()
}
return (
<div>
<h2 ref={titleRef}>Hello World</h2>
<input type="text" ref={inputRef} />
<button onClick={showTitleDom}>查看title的dom</button>
</div>
)
})
export default App
自定义Hook
- 自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性
- 自定义Hook的函数必须以use开头
- 需求:所有的组件在创建和销毁时都进行打印
- 组件被创建:打印 组件被创建了
- 组件被销毁:打印 组件被销毁了
-
举例
import React, { memo, useEffect, useState } from 'react' // 自定义hook,必须用use开头的函数定义 function useLogLife(cName) { useEffect(() => { console.log(cName + "组件被创建") return () => { console.log(cName + "组件被销毁") } }, [cName]) } const Home = memo(() => { useLogLife("home") return <h1>Home Page</h1> }) const About = memo(() => { useLogLife("about") return <h1>About Page</h1> }) const App = memo(() => { const [isShow, setIsShow] = useState(true) useLogLife("app") return ( <div> <h1>App Root Component</h1> <button onClick={e => setIsShow(!isShow)}>切换</button> { isShow && <Home/> } { isShow && <About/> } </div> ) }) export default App