React Hooks
Hooks其实就是有状态的函数式组件。
React Hooks让React的成本降低了很多,体现为:
- 生命周期可以不用学,react hooks使用全新的理念来管理组件的运作过程
- 高阶组件不用学。React hooks能够完美解决高阶组件想要解决的问题,并且更靠谱
-
redux
不再是必须品。我们能够通过其他方式管理组件状态。
和class语法相比,函数组件比较受欢迎,但是以前的函数组件无法维护自己的状态,因此很多时候不得不选择class来实现目的
React Hooks让函数组件维护内部状态成为了可能(让函数组件能够拥有自己的状态)
能够更简单地与TypeScript
结合,相对于class组件,简单得多
闭包,是React Hooks的核心
闭包是一个特殊的对象,它有两部分组成,执行上下文的A以及在A中创建的函数B,当B执行时,如果访问了A中的变量对象,那么闭包就会产生。
const A = () => { // 执行上下文,这里暂时写为一个函数,它也可以指一个模块
const a = "aaa"
const B = () => { // 在A中创建的函数B
console.log(a) // 注意这里可以修改a的值
// 当B执行时,如果访问了A中的变量,那就产生闭包,这里称A(执行上下文)为闭包
}
}
当我们定义一个React组件,并且在其他模块中使用,这也会形成闭包。
// A.js
export const A = () => { // 这里把A.js看成是一个模块
const a = 'aaa'
}
// B.js
import { A } from './A.js'
const B = () => { // 这里把B.js看成是另一个组件,引入A组件,并且访问A组件中的变量,此时形成闭包
console.log(a) // 注意这里可以修改a的值
}
解析:这里我们把A模块看成是state模块,里面的a变量是state变量,而在B组件中,我们可以访问到state(a变量)并且可以修改他(相当于我们修改状态state),那么根据闭包的特性,state模块中的state变量,会持久存在,因此当B函数再次执行时,我们也能获取到上一次B函数执行结束时state的值。
对上面的代码的补充:
// state.js模块 对应上面的A.js
let state = null;
export const useState = (value) => {
// 第一次调用的时候没有初始值,因此使用传入的初始值赋值
state = state || value
function dispatch(newValue) => {
state = newValue
}
return [state, dispatch]
}
// 在其他模块中引入并使用 对应上面的B.js 相当于是一个hooks组件
import React from 'react'
import { useState } from './state'
export default function Demo() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
这就是React Hooks能够让函数拥有内部状态的基本原理。
快速分析一个函数的作用,一个思路是看它返回了什么,二个思路是看它改变了什么。
React Hooks中的API
useState
由于react中是采用自上而下单向数据流的方式,来管理自身的数据与状态,数据只能由父组件触发,向下传递到子组件。所以在React中,state与props的改变,都会引发组件重新渲染,如果是父组件的变化,则该父组件下面的子组件都会重新渲染。
组件重新渲染:
- class组件: 重新执行render方法
- 函数式组件: 整个函数重新执行
函数式组件:
- 函数式组件接收props作为自己的参数
- props的每次变动,组件都会重新渲染一次,也就是函数重新执行一次
- 没有this,所以也就省略了class组件中需要绑定this的操作
useState
利用闭包,在函数内部创建一个当前函数组件的状态,并提供一个修改该状态的方法setState
useState
接收一个值作为当前状态的初始值,并且初始化操作只有组件首次渲染才会执行。如果初始值需要通过较为复杂的计算得出,则可以传入一个函数作为参数,函数返回值为初始值。该函数也只会在组件首次渲染时执行一次。
const a = 10
const b = 20
const [count, setCount] = useState(() => {
return a + b
})
注意:如果状态值state是一个引用类型,我们想通过setCount()
方法来改变其中一部分,我们应该这样来修改它
const [count, setCount] = useState({a: 1, b: 2}) // count的默认值是{a: 1, b: 2}
setCount({...count, b: 4}) // 修改之后的值是{a: 1, b: 4}
注意: 无论是在class中,还是在hooks中, state的改变,都是异步的
状态异步,也就是说当我们想要在setCount
之后立即去使用它时,无法拿到状态最新的值,而是得等到下一个事件循环周期执行时(也就是下一次setCount
时),状态才是最新的值。
假设我们想实现一个查询接口,当输入关键字(不断改变查询条件,即我们需要传入的参数),点击搜索时,立即重新去请求一次数据,可以这样实现:
export default function AsyncDemo() {
const [params] = useState({}) // 1.没有引入setParams
const [listData, setListData] = useState([])
const fetchListData = () => {
listApi(params).then(res => {
setListData(res.data)
})
}
const searchByName = (name) => {
params.name = name // 2.这里访问到外部的params变量,并修改它的值,注意这里没有用setParams()来进行修改的,所以不是异步。
fetchListData()
}
return (
<button onClick={() => searchByName('Jhon')}>search</button>
)
}
useEffect(待补充...)
在函数式组件中 ,每当DOM完成一次渲染,都会有对应的副作用执行, useEffect
用于提供自定义的执行内容,它的第一个参数(作为函数传入)就是自定义的执行内容。为了避免反复执行, 传入第二个参数(由需要监听的值组成的数组)作为比较变化(浅比较)的依赖,比较之后如果值保持不变,副作用逻辑就不再执行。
使用场景:比如有一个列表,我们想做一个随时点击刷新或者下拉刷新数据,应该怎么操作?
常规思维是定义一个请求列表数据的方法,每次需要刷新的时候执行即可,而hook中的思维不同:
创建一个变量,来作为变化值,实现目的的同时防止循环执行
import React, { useState, useEffect } from 'react'
export default function Demo() {
const [list, setList] = useState(0)
const [loading, setLoading] = useState(true)
// DOM渲染完成后副作用执行
useEffect(() => {
if (loading) {
recordListApi().then(res => {
setList(res.data)
})
}
}, [loading])
return (
<div>
<button onClick={() => setLoading(true)}>刷新</button>
<FlatList data={list} />
</div>
)
}
hooks中组件的传值:
- 父传子, 父组件通过自定义属性,属性值是需要传递给子组件的值, 子组件通过props来接收
// 父组件
import React, { useState } from 'react'
import Child from './Child'
export default function Father() {
const [count, setCount] = useState(0) // 1. 在父组件里定义状态以及修改状态函数
return (
<div>
<h1>父组件, 我点击的次数是: {count}</h1>
<hr />
<Child msg={count} /> {/* 2. 这里自定义属性, 传递值给子组件 */}
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
)
}
// 子组件
import React from 'react'
export default function Child({msg}) { // 3. 子组件接收的参数直接在这里解构
// const { msg } = props
return (
<div>
<p>子组件, 我接收的次数是: {msg}</p>
</div>
)
}
- 子传父, 子组件通过事件触发父组件里面的自定义事件,并把参数传过去,让父组件自己修改父组件自身定义的状态
// 父组件
import React, { useState } from 'react'
import Child from './Child'
export default function Father() {
const [count, setCount] = useState(0) // 1. 在父组件里定义状态以及修改状态函数
return (
<div>
<h1>父组件, 我接受要修改的值是: {count}</h1>
<hr />
<Child msg={count} changeCount={setCount} /> {/* 2. 这里自定义属性和事件, 传递值给子组件 */}
</div>
)
}
// 子组件
import React from 'react'
export default function Child({ msg, changeCount }) {
return (
<div>
<p>子组件, 我点击的次数是: {msg}</p>
<button onClick={() => changeCount(msg + 1)}>点击</button> {/* 3.子组件直接通过点击事件来触发父组件里面的自定义事件,让他自己修改*/}
</div>
)
}
- 跨级组件的传值, 利用useContext获取顶级组件传过来的值
// 父组件
import React, { useState, useContext, createContext } from 'react'
import Child from './Child'
// 1. 创建Context组件, 并导出供下面的组件引入
export const CountContext = createContext(0) // 初始化状态
export default function Father() {
const [count, setCount] = useState(0)
return (
<div>
<h1>父组件, 我点击的次数是: {count}</h1>
<hr />
<CountContext.provider value={count}> {/*2. 将需要获取到父组件的值的组件包起来,并把要传递下去的值赋给value属性*/}
<Child />
</CountContext.provider>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
)
}
// 子组件
import React, { useContext } from 'react'
import Grandson from './Grandson'
import { CountContext } from './Father'
export default function Child() {
const count = useContext(CountContext) // 3. 这里可以接收到父组件传过来的值
return (
<h2>子组件, 从父组件接收的点击次数是: {count}</h2>
<hr />
<Grandson />
)
}
// 孙子组件
import React, { useContext } from 'react'
import { CountContext } from './Father'
export default function Grandson() {
const count = useContext(CountContext)
return (
<div>
<h3>孙子组件, 从祖父组件接收的点击次数是: {count}</h3>
</div>
)
}
自定义Hooks
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中—— 实现逻辑复用
当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中,而组件和Hook都是函数,所以也适用这种方式。
这里举一个比较简单的场景,比如在两个不同的页面,分别有各自的按钮,点击按钮后会弹出模态框。这时我们就可以使用自定义Hooks来提取公用的逻辑
- 编写custom hooks
// useModal.js文件
import { useState } from 'react'
export default function useModal() { // 注意自定义Hooks时, 要以use开头
// 1. 初始化模态框的visible,用来控制显示 或 隐藏
const [visible, setVisible] = useState(false)
// 2. 定义逻辑处理函数,这里是控制模态框显示
function handleClick() {
setVisible(true)
}
// 同上,ok按钮事件
function handleOk() {
setVisible(false)
}
// 同上, cancel按钮事件
function handleCancel() {
setVisible(false)
}
// 3. 统一将需要用到的状态和事件处理函数return 出去,供外面组件使用
return {
visible,
handleClick,
handleOk,
handleCancel
}
}
- 使用custom hooks
// first page
import React from 'react'
import { Button, Modal } from 'antd'
import useModal from './useModal' // 1. 引入自定义Hooks
export default function FirstPage() {
const { visible, handleClick, handleOk, handleCancel } = useModal() // 2. 调用自定义Hooks, 获取状态和事件处理函数
return (
<div>
<h2>First Page</h2>
<Button onClick={handleClick}>alert 1</Button>
<Modal
title="First Page Modal"
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
>
<p>First Page Some Contents...</p>
</Modal>
</div>
)
}
// second page
import React from 'react'
import { Button, Modal } from 'antd'
import useModal from './useModal'
export default function SecondPage() {
const { visible, handleClick, handleOk, handleCancel } = useModal()
return (
<div>
<h2>Second Page</h2>
<Button onClick={handleClick}>alert 2</Button>
<Modal
title="Second Page Modal"
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Second Page Some Contents...</p>
</Modal>
</div>
)
}
效果:
first page:
first.pngsecond page:
second.png
网友评论