【React】React Hook解读

react

目录

  • 什么是React Hook?
  • 使用React Hook的目的是什么?
  • 什么时候会用React Hook?
  • React Hook的规则是什么?
  • 什么是Hook的副作用?

    • 需要清理的effect:

      • effect性能优化:

  • 常见Hooks API

    • useState
    • useMemo
    • useCallback
    • useRef
    • useEffect
    • useLayoutEffect
    • useContext 共享状态钩子
    • useReducer():action 钩子
    • useImperativeHandle

      • ref和forwardRef
      • useImperativeHandle

    • useState 与useContext实例
    • 更多自定义Hooks

      • 自定义useTtitle
      • 自定义Hooks的规则

Hook 是一个特殊的函数,它可以让你”钩入”React state及生命周期等特性的函数。例如,useState是允许你在React函数组件中添加state的Hook。

使用React Hook的目的是什么?

使用Hook其中一个目的就是要解决class中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到几个不同方法中的问题。

什么时候会用React Hook?

如果你在编写函数组件并意识到需要向其添加一些state,以前的做法是必须将其它转化为class。现在你可以在现有的函数组件中使用Hook。

React Hook的规则是什么?

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。

什么是Hook的副作用?

之前可能已经在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。

通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里。

Effect Hook 可以让你在函数组件中执行副作用操作。

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

useEffect 会在每次渲染后都执行吗?

是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行。

有的Hook是需要清理的(比如定时器),有的是不需要清理的的(比如数据获取)。需要清理的副作用,需要返回一个函数。

不需要清理的effect:

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

function Example() {

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

useEffect(() => {

document.title = `You clicked ${count} times`;

});

return (

<div>

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

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

Click me

</button>

</div>

);

}

需要清理的effect:

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

function FriendStatus(props) {

const [isOnline, setIsOnline] = useState(null);

useEffect(() => {

function handleStatusChange(status) {

setIsOnline(status.isOnline);

}

ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

// Specify how to clean up after this effect:

return function cleanup() {

ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);

};

});

if (isOnline === null) {

return 'Loading...';

}

return isOnline ? 'Online' : 'Offline';

}

effect性能优化:

如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:

useEffect(() => {

document.title = `You clicked ${count} times`;

}, [count]); // 仅在 count 更改时更新

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

更多详细Hook API,详见:Hook API

常见Hooks API

useState

设置和改变state,代替原来的state和setState。

import React, {useState} from 'react';

function RenderStateInstance({initialState}) {

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

return (

<>

Count: {count}<br />

<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>

<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>

</>

);

}

useMemo

控制组件更新条件,可根据状态变化控制方法执行,优化传值。

import React, { useState, useMemo } from 'react'

function calcNumber(count) {

console.log("cal重新计算");

let total = 0;

for (let i = 1; i <= count; i++) {

total += i;

}

return total

}

export default function MemoHookDemo01() {

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

const [show, setShow] = useState(true);

// const total = calcNumber(count);

const total = useMemo(() => {

return calcNumber(count);

}, [count])

return (

<div>

<h2>计算数字的和:{total}</h2>

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

<button onClick={e => { setShow(!show) }}>show切换</button>

</div>

)

}

这个例子中:

useMemo第一个参数为回调函数,第二个参数为依赖项;

当依赖项发生改变时,执行回调函数。

useCallback

useMemo优化传值,usecallback优化传的方法,是否更新。

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

const set = new Set();

export default function Callback() {

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

const [val, setVal] = useState('');

const callback = useCallback(() => {

console.log(count);

}, [count]);

set.add(callback);

return <div>

<h4>{count}</h4>

<h4>{set.size}</h4>

<div>

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

<input value={val} onChange={event => setVal(event.target.value)}/>

</div>

</div>;

}

使用场景:

有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

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

function Parent() {

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

const [val, setVal] = useState('');

const callback = useCallback(() => {

return count;

}, [count]);

return <div>

<h4>{count}</h4>

<Child callback={callback}/>

<div>

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

<input value={val} onChange={event => setVal(event.target.value)}/>

</div>

</div>;

}

function Child({ callback }) {

const [count, setCount] = useState(() => callback());

useEffect(() => {

setCount(callback());

}, [callback]);

return <div>

{count}

</div>

useRef

跟以前的ref,一样,只是更简洁了。它可以用来获取组件实例对象或者是DOM对象。

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

export default function App(props){

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

const doubleCount = useMemo(() => {

return 2 * count;

}, [count]);

const couterRef = useRef();

useEffect(() => {

document.title = `The value is ${count}`;

console.log(couterRef.current);

}, [count]);

return (

<>

<button ref={couterRef} onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}</button>

</>

);

}

代码中用useRef创建了couterRef对象,并将其赋给了button的ref属性。这样,通过访问couterRef.current就可以访问到button对应的DOM对象。

而useRef这个hooks函数,除了传统的用法之外,它还可以“跨渲染周期”保存数据。

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

export default function App(props){

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

const doubleCount = useMemo(() => {

return 2 * count;

}, [count]);

const timerID = useRef();

useEffect(() => {

timerID.current = setInterval(()=>{

setCount(count => count + 1);

}, 1000);

}, []);

useEffect(()=>{

if(count > 10){

clearInterval(timerID.current);

}

});

return (

<>

<button ref={couterRef} onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}</button>

</>

);

}

useEffect

页面加载完成以后,想要做的一些操作,这个经常是componentDidMount里面做的一些事情,但是不会block browser painting。

代替原来的生命周期,componentDidMount,componentDidUpdate 和 componentWillUnmount 的合并版。

function Counter() {

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

useEffect(() => {

setTimeout(() => {

console.log(count)

}, 3000)

})

// 在3秒内快速点击5次按钮,控制台打出的结果是什么样的?

// 0 1 2 3 4 5

return (

<div>

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

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

Click me

</button>

</div>

);

}

useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。

useContext 共享状态钩子

createContext 能够创建一个 React 的 上下文(context),然后订阅了这个上下文的组件中,可以拿到上下文中提供的数据或者其他信息。

import React, { useContext } from "react";

import ReactDOM from "react-dom";

const TestContext= React.createContext({});

const Navbar = () => {

const { username } = useContext(TestContext)

return (

<div className="navbar">

<p>{username}</p>

</div>

)

}

const Messages = () => {

const { username } = useContext(TestContext)

return (

<div className="messages">

<p>1 message for {username}</p>

</div>

)

}

function App() {

return (

<TestContext.Provider

value={{

username: 'superawesome',

}}

>

<div className="test">

<Navbar />

<Messages />

</div>

<TestContext.Provider/>

);

}

const rootElement = document.getElementById("root");

ReactDOM.render(<App />, rootElement);

useReducer():action 钩子

Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState。

useReducers()钩子用来引入 Reducer 功能。

const [state, dispatch] = useReducer(reducer, initialState);

上面是useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。

下面是一个计数器的例子。用于计算状态的 Reducer 函数如下。

const myReducer = (state, action) => {

switch(action.type) {

case('countUp'):

return {

...state,

count: state.count + 1

}

default:

return state;

}

}

组件代码如下。

function App() {

const [state, dispatch] = useReducer(myReducer, { count: 0 });

return (

<div className="App">

<button onClick={() => dispatch({ type: 'countUp' })}>

+1

</button>

<p>Count: {state.count}</p>

</div>

);

}

useImperativeHandle

useImperativeHandle可以让你在使用ref时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用ref这样的命令式代码。useImperativeHandle应当与forwardRef一起使用。

ref和forwardRef

我们先来看下ref和forwardRef的使用。

  • 通过forwardRef可以将ref转发给子组件
  • 子组件拿到父组件创建的ref, 绑定到自己的某一个元素中

import React, { useRef, forwardRef } from 'react'

// forwardRef可以将ref转发给子组件

const JMInput = forwardRef((props, ref) => {

return <input type="text" ref={ref} />

})

export default function ForwardDemo() {

// forward用于获取函数式组件DOM元素

const inputRef = useRef()

const getFocus = () => {

inputRef.current.focus()

console.log(inputRef.current);

}

return (

<div>

<button onClick={getFocus}>聚焦</button>

<JMInput ref={inputRef} />

</div>

)

}

forwardRef的做法本身没有什么问题, 但是我们是将子组件的DOM直接暴露给了父组件:

  • 直接暴露给父组件带来的问题是某些情况的不可控
  • 父组件可以拿到DOM后进行任意的操作
  • 我们只是希望父组件可以操作的focus,其他并不希望它随意操作其他方法

比如上面的inputRef.current打印出来的就是一个DOM。

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle使用简单总结:

作用: 减少暴露给父组件获取的DOM元素属性, 只暴露给父组件需要用到的DOM方法

参数1: 父组件传递的ref属性

参数2: 返回一个对象, 以供给父组件中通过ref.current调用该对象中的方法

通过useImperativeHandle可以只暴露特定的操作。通过useImperativeHandle的Hook, 将父组件传入的ref和useImperativeHandle第二个参数返回的对象绑定到了一起,所以在父组件中, 调用inputRef.current时, 实际上是返回的对象

import React, { useRef, forwardRef, useImperativeHandle } from 'react'

const JMInput = forwardRef((props, ref) => {

const inputRef = useRef()

// 作用: 减少父组件获取的DOM元素属性,只暴露给父组件需要用到的DOM方法

// 参数1: 父组件传递的ref属性

// 参数2: 返回一个对象,父组件通过ref.current调用对象中方法

useImperativeHandle(ref, () => ({

focus: () => {

inputRef.current.focus()

},

test: () => {

console.log('abcd');

}

}))

return <input type="text" ref={inputRef} />

})

export default function ImperativeHandleDemo() {

// useImperativeHandle 主要作用:用于减少父组件中通过forward+useRef获取子组件DOM元素暴露的属性过多

// 为什么使用: 因为使用forward+useRef获取子函数式组件DOM时,获取到的dom属性暴露的太多了

// 解决: 使用uesImperativeHandle解决,在子函数式组件中定义父组件需要进行DOM操作,减少获取DOM暴露的属性过多

const inputRef = useRef();

const action = () => {

inputRef.current.focus();

inputRef.current.test();

console.log(inputRef.current);

}

return (

<div>

<button onClick={action}>聚焦</button>

<JMInput ref={inputRef} />

</div>

)

}

所以上面inputRef.current返回的是一个对象:

useState 与useContext实例

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

const ThemeContext = React.createContext('light');

function RenderStateInstance({initialState}) {

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

const contextType = useContext(ThemeContext);;

return (

<>

Count: {count}<br />

Theme: {contextType}<br /><br />

<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>

<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>

</>

);

}

export default class Mine extends React.Component {

render() {

let initialState = 0;

return (

<ThemeContext.Provider value="dark">

<div>

<p>My 页面 </p>

<RenderStateInstance initialState={initialState} />

</div>

</ThemeContext.Provider>

)

}

}

更多自定义Hooks

React 官网:https://zh-hans.reactjs.org/docs/hooks-custom.html

更多Github实例:https://github.com/ecomfe/react-hooks

自定义useTtitle

import { useEffect } from 'react';

const useTitle = title => {

useEffect(() => {

document.title = title;

}, [])

return;

};

export default useTitle;

使用useTitle:

import React from 'react';

import useTitle from '../hooks/useTitle';

const Title = () => {

useTitle('修改Title')

return (

<>

<p>这里用了自定义的Hooks title</p>

</>

);

};

export default Title;

自定义Hooks的规则

  • 自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性。
  • 自定义 Hook 必须以 “use” 开头

以上是 【React】React Hook解读 的全部内容, 来源链接: utcz.com/z/383672.html

回到顶部