美文网首页
React Hooks

React Hooks

作者: Joemoninni | 来源:发表于2020-09-29 16:34 被阅读0次

React Hooks

Hooks其实就是有状态的函数式组件。

React Hooks让React的成本降低了很多,体现为:

  1. 生命周期可以不用学,react hooks使用全新的理念来管理组件的运作过程
  2. 高阶组件不用学。React hooks能够完美解决高阶组件想要解决的问题,并且更靠谱
  3. 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的改变,都会引发组件重新渲染,如果是父组件的变化,则该父组件下面的子组件都会重新渲染。

组件重新渲染:

  1. class组件: 重新执行render方法
  2. 函数式组件: 整个函数重新执行

函数式组件:

  1. 函数式组件接收props作为自己的参数
  2. props的每次变动,组件都会重新渲染一次,也就是函数重新执行一次
  3. 没有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中组件的传值:

  1. 父传子, 父组件通过自定义属性,属性值是需要传递给子组件的值, 子组件通过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>
    )
}
  1. 子传父, 子组件通过事件触发父组件里面的自定义事件,并把参数传过去,让父组件自己修改父组件自身定义的状态
// 父组件

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>
    )
}
  1. 跨级组件的传值, 利用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来提取公用的逻辑

  1. 编写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
    }
}
  1. 使用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.png

second page:

second.png

相关文章

  • React Hooks

    React Hooks Hooks其实就是有状态的函数式组件。 React Hooks让React的成本降低了很多...

  • react-hooks

    前置 学习面试视频 总结react hooks react-hooks react-hooks为函数组件提供了一些...

  • React Hooks

    前言 React Conf 2018 上 React 提出了关于 React Hooks 的提案,Hooks 作为...

  • 5分钟简单了解React-Hooks

    首先附上官网正文?:React Hooks Hooks are a new addition in React 1...

  • react-hooks

    react-hooks react-hooks 是react16.8以后,react新增的钩子API,目的是增加代...

  • React-hooks API介绍

    react-hooks HOOKS hooks概念在React Conf 2018被提出来,并将在未来的版本中被...

  • React Hooks 入门

    React Hooks 是 React v16.8 版本引入了全新的 API。 React Hooks 基本概念 ...

  • react hooks 源码分析 --- useState

    1. react hooks简介 react hooks 是react 16.8.0 的新增特性,它可以让你在不编...

  • React Hooks的入门简介

    什么是React Hooks? 首先React Hooks是React生态圈里的新特性,它改变了传统react的开...

  • React hooks(钩子)

    React hooks(钩子) React hooks 是React 16.8中的新增功能。它们使您无需编写类即可...

网友评论

      本文标题:React Hooks

      本文链接:https://www.haomeiwen.com/subject/kcveuktx.html