函数组件,没有 class 组件中的 componentDidMount、componentDidUpdate 等生命周期方法,也没有 State,但这些可以通过 React Hook 实现。

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来

React Hooks优点

  1. 代码可读性更强:通过 React Hooks 可以将功能代码聚合,方便阅读维护
  2. 组件树层级变浅:在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现

九种常用的钩子

  1. useState:保存组件状态
  2. useEffect: 处理副作用
  3. useContext: 减少组件层级
  4. useReducer:类似于redux,通信
  5. useCallback: 记忆函数
  6. useMemo: 记忆组件
  7. useRef: 保存引用值
  8. useImperativeHandle: 透传 Ref
  9. useLayoutEffect: 同步执行副作用

1、useState保存组件状态

用来代替:state,setState

若使用对象做 State,useState 更新时会直接替换掉它的值,而不像 setState 一样把更新的字段合并进对象中。推荐将 State 对象分成多个 State 变量。

类组件案例

在类组件中,我们使用 this.state 来保存组件状态,并对其修改触发组件重新渲染。

import React from "react";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
name: "alife"
};
}
render() {
const { count } = this.state;
return (
<div>
Count: {count}
<button onClick={() => this.setState({ count: count + 1 })}>+</button>
<button onClick={() => this.setState({ count: count - 1 })}>-</button>
</div>
);
}
}

函数组件useState

在函数组件中,由于没有 this 这个黑魔法,React 通过 useState 来帮我们保存组件的状态。

  1. 通过传入 useState 参数后返回一个带有默认状态和改变状态函数的数组[obj, setObject] (名称是自定义的可修改[count, setCount]...)。
  2. 通过传入 新状态给函数 来改变原本的状态值。
import React, { useState } from "react";
function App() {
const [obj, setObject] = useState({
count: 0,
name: "alife"
});
let [count, setCount] = useState(0)
return (
<div className="App">
Count: {obj.count}
<button onClick={() => setObject({ ...obj, count: obj.count + 1 })}>+</button>
<button onClick={() => setObject({ ...obj, count: obj.count - 1 })}>-</button>
</div>
);
}

注意: useState 不帮助你处理状态,相较于 setState 非覆盖式更新状态,useState 覆盖式更新状态,需要开发者自己处理逻辑。(代码如上)

2、useEffect 处理副作用

用来代替:componentDidMount、componentDidUpdate和componentWillUnmount的组合体。

默认情况下,useEffect会在第一次渲染之后和每次更新之后执行,每次运行useEffect时,DOM 已经更新完毕。

为了控制useEffect的执行时机与次数,可以使用第二个可选参数施加控制。

类组件案例

在例子中,组件每隔一秒更新组件状态,并且每次触发更新都会触发 document.title 的更新(副作用),而在组件卸载时修改 document.title(类似于清除)

从例子中可以看到,一些重复的功能开发者需要在 componentDidMount 和 componentDidUpdate 重复编写,而如果使用 useEffect 则完全不一样。

import React, { Component } from "react";
class App extends Component {
state = {
count: 1
}; componentDidMount() {
const { count } = this.state;
document.title = "componentDidMount" + count;
this.timer = setInterval(() => {
this.setState(({ count }) => ({
count: count + 1
}));
}, 1000);
} componentDidUpdate() {
const { count } = this.state;
document.title = "componentDidMount" + count;
} componentWillUnmount() {
document.title = "componentWillUnmount";
clearInterval(this.timer);
} render() {
const { count } = this.state;
return (
<div>
Count:{count}
<button onClick={() => clearInterval(this.timer)}>clear</button>
</div>
);
}
}

useEffect

参数1:接收一个函数,可以用来做一些副作用比如异步请求,修改外部参数等行为。

参数2:称之为dependencies,是一个数组,如果数组中的值变化才会触发 执行useEffect 第一个参数中的函数。返回值(如果有)则在组件销毁或者调用函数前调用

    1. 比如第一个 useEffect 中,理解起来就是一旦 count 值发生改变,则修改 documen.title 值;
    1. 而第二个 useEffect 中传递了一个空数组[],这种情况下只有在组件初始化或销毁的时候才会触发,用来代替 componentDidMount 和 componentWillUnmount,慎用;
    1. 还有另外一个情况,就是不传递第二个参数,也就是useEffect只接收了第一个函数参数,代表不监听任何参数变化。每次渲染DOM之后,都会执行useEffect中的函数。
import React, { useState, useEffect } from "react";
let timer = null;
function App() {
const [count, setCount] = useState(0); useEffect(() => {
document.title = "componentDidMount" + count;
},[count]); useEffect(() => {
timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
// 一定注意下这个顺序:
// 告诉react在下次重新渲染组件之后,同时是下次执行上面setInterval之前调用
return () => {
document.title = "componentWillUnmount";
clearInterval(timer);
};
}, []); return (
<div>
Count: {count}
<button onClick={() => clearInterval(timer)}>clear</button>
</div>
);
}

3、useContext 减少组件层级

用来代替:Provider, Consumer

处理多层级传递数据的方式,在以前组件树种,跨层级祖先组件想要给孙子组件传递数据的时候,除了一层层 props 往下透传之外,我们还可以使用 React Context API 来帮我们做这件事

类组件案例

const { Provider, Consumer } = React.createContext(null);
function Bar() {
return <Consumer>{color => <div>{color}</div>}</Consumer>;
} function Foo() {
return <Bar />;
} function App() {
return (
<Provider value={"grey"}>
<Foo />
</Provider>
);
}

useContext使用的方法

  1. 要先创建createContex

    使用createContext创建并初始化
    import  { createContext } from 'react'
    const C = createContext(null);
  2. Provider 指定使用的范围

    在圈定的范围内,传入读操作和写操作对象,然后可以使用上下文
        <C.Provider value={{n,setN}}>
    这是爷爷
    <Baba></Baba>
    </C.Provider>
  3. 最后使用useContext

    使用useContext接受上下文,因为传入的是对象,则接受的也应该是对象
    const {n,setN} = useContext(C);
import React, { createContext, useContext, useReducer, useState } from 'react'
import ReactDOM from 'react-dom'
// 创造一个上下文
const C = createContext(null);
function App(){
const [n,setN] = useState(0)
return(
// 指定上下文使用范围,使用provider,并传入读数据和写入据
<C.Provider value={{n,setN}}>
这是爷爷
<Baba></Baba>
</C.Provider>
)
} function Baba(){
return(
<div>
这是爸爸
<Child></Child>
</div>
)
} function Child(){
// 使用上下文,因为传入的是对象,则接受也应该是对象
const {n,setN} = useContext(C)
const add=()=>{
setN(n=>n+1)
};
return(
<div>
这是儿子:n:{n}
<button onClick={add}>+1</button>
</div>
)
} ReactDOM.render(<App />,document.getElementById('root'));

4、useReducer 数据交互

用来代替:Redux/React-Redux

唯一缺少的就是无法使用 redux 提供的中间件

import React, { useReducer } from "react";

const initialState = {
count: 0
}; function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + action.payload };
case "decrement":
return { count: state.count - action.payload };
default:
throw new Error();
}
} function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment", payload: 5 })}>
+
</button>
<button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
-
</button>
</>
);
}

5、useCallback 记忆函数

减少重复渲染

老规矩,第二个参数传入一个数组,数组中的每一项一旦值或者引用发生改变,useCallback 就会重新返回一个新的记忆函数提供给后面进行渲染。

这样只要子组件继承了 PureComponent 或者使用 React.memo 就可以有效避免不必要的 VDOM 渲染。

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

const Child = memo(function(props) {
console.log('child run...')
return (
<>
<h1>hello</h1>
<button onClick={props.onAdd}>add</button>
</>
)
}) export default function UseCallback() {
console.log('parent run...')
let [ count, setCount ] = useState(0) const handleAdd = useCallback(
() => {
console.log('added.')
},
[],
)
return (
<div>
<div>{count}</div>
<Child onAdd={handleAdd}></Child>
<button onClick={() => setCount(100)}>change count</button>
</div>
)
}

6、useMemo 记忆组件

useCallback 的功能完全可以由 useMemo 所取代,如果你想通过使用 useMemo 返回一个记忆函数也是完全可以的。

区别:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你

function App() { const memoizedHandleClick = useMemo(() => () => { console.log('Click happened') }, []); // 空数组代表无论什么情况下该函数都不会发生改变 return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>; }

  • useCallback: 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。
  • useMemo: 更适合经过函数计算得到一个确定的值,比如记忆组件。
    function Parent({ a, b }) {
    const child1 = useMemo(() => () => <Child1 a={a} />, [a]);
    const child2 = useMemo(() => () => <Child2 b={b} />, [b]);
    return (
    <>
    {child1}
    {child2}
    </>
    )
    }

7、useRef 保存引用值

用来代替:createRef

Ref

React提供了一个属性ref,用于表示对组价实例的引用,其实就是ReactDOM.render()返回的组件实例:

  • ReactDOM.render()渲染组件时返回的是组件实例;
  • 渲染dom元素时,返回是具体的dom节点。

ref可以挂载到组件上也可以是dom元素上;

  • 挂到组件(class声明的组件)上的ref表示对组件实例的引用。不能在函数式组件上使用 ref 属性,因为它们没有实例:
  • 挂载到dom元素上时表示具体的dom元素节点。

useRef()

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

  • 可以通过 ref.current 值访问组件或真实的 DOM 节点,从而可以对 DOM 进行一些操作,比如监听事件等等
    export default function App() {
    const count = useRef(0)
    <!-- count.current存储状态值 -->
    const handleClick = (num) => {
    count.current += num
    setTimeout(() => {
    console.log("count: " + count.current);
    }, 3000)
    } return (
    <div>
    <p>You clicked {count.current} times</p>
    <button onClick={() => handleClick(1)}>增加 count</button>
    <button onClick={() => handleClick(-1)}>减少 count</button>
    </div>
    );
    }

8、useImperativeHandle 透传 Ref

通过 useImperativeHandle 用于让父组件获取子组件内的索引

useImperativeHandle 应当与 forwardRef 一起使用

useImperativeHandle(ref, createHandle, [deps])

  • ref:定义 current 对象的 ref createHandle:一个函数,返回值是一个对象,即这个 ref 的 current
  • [deps]:即依赖列表,当监听的依赖发生变化,useImperativeHandle 才会重新将子组件的实例属性输出到父组件
  • 注意:ref 的 current 属性上,如果为空数组,则不会重新输出。
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react";

function ChildInputComponent(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => inputRef.current);
return <input type="text" name="child input" ref={inputRef} />;
} const ChildInput = forwardRef(ChildInputComponent); function App() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return (
<div>
<ChildInput ref={inputRef} />
</div>
);
} export default App

9、useLayoutEffect 同步执行副作用

大部分情况下,使用 useEffect 就可以帮我们处理组件的副作用,但是如果想要同步调用一些副作用,比如对 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行。

(1) useEffect和useLayoutEffect有什么区别?

简单来说就是调用时机不同

  • useLayoutEffect:和原来componentDidMount&componentDidUpdate一致,在react完成DOM更新后马上同步调用的代码,会阻塞页面渲染。
  • useEffect:是会在整个页面渲染完才会调用的代码。
  • 官方建议优先使用useEffect

在实际使用时如果想避免页面抖动(在useEffect里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在useLayoutEffect里。关于使用useEffect导致页面抖动。

不过useLayoutEffect在服务端渲染时会出现一个warning,要消除的话得用useEffect代替或者推迟渲染时机。

import React from 'react'
import { useState, useEffect } from 'react' export default function (props) {
let [data, setData] = useState({count: 0}) function loadData() {
return fetch('http://localhost:8080/api/movies/list')
.then(response => response.json())
.then(result => {
return result
})
} useEffect(() => {
console.log('effect')
}, [data]) useEffect(() => {
console.log('mounted.') ;(async ()=>{
let result = await loadData()
console.log(result)
})() // return () => {
// console.log('unmout')
// }
}, []) return (
<>
<div>{data.count}</div>
<button onClick={() => setData(data => ({count: data.count + 1}))}>click</button>
</>
)
}

react之react Hooks的更多相关文章

  1. React 16.x & Hooks

    React 16.x & Hooks Hooks https://reactjs.org/docs/hooks-intro.html https://reactjs.org/docs/hook ...

  2. 10分钟了解 react 引入的 Hooks

    "大家好,我是谷阿莫,今天要将的是一个...",哈哈哈,看到这个题我就想到这个开头.最近react 官方在 2018 ReactConf 大会上宣布 React v16.7.0-a ...

  3. [React] Fix "React Error: Rendered fewer hooks than expected"

    In this lesson we'll see an interesting situation where we're actually calling a function component ...

  4. React实战教程之从零开始手把手教你使用 React 最新特性Hooks API 打造一款计算机知识测验App

    项目演示地址 项目演示地址 项目代码结构 前言 React 框架的优雅不言而喻,组件化的编程思想使得React框架开发的项目代码简洁,易懂,但早期 React 类组件的写法略显繁琐.React Hoo ...

  5. React学习笔记-1-什么是react,react环境搭建以及第一个react实例

    什么是react?react的官方网站:https://facebook.github.io/react/下图这个就是就是react的标志,非常巧合的是他和我们的github的编辑器Atom非常相似. ...

  6. 小谈React、React Native、React Web

    React有三个东西,React JS 前端Web框架,React Native 移动终端Hybrid框架,React Web是一个源码转换工具(React Native 转 Web,并之所以特别提出 ...

  7. React的React Native

    React的React Native React无疑是今年最火的前端框架,github上的star直逼30,000,基于React的React Native的star也直逼20,000.有了React ...

  8. React Navigation & React Native & React Native Navigation

    React Navigation & React Native & React Native Navigation React Navigation https://facebook. ...

  9. 重谈react优势——react技术栈回顾

    react刚刚推出的时候,讲react优势搜索结果是几十页. 现在,react已经慢慢退火,该用用react技术栈的已经使用上,填过多少坑,加过多少班,血泪控诉也不下千文. 今天,再谈一遍react优 ...

随机推荐

  1. js 改变this指向的三种方法 bind call apply

    先了解下bind call apply 的注意点 bind 需要手动调用 第一个参数 this 要指向的对象,后面是 散列的参数 call 不需要手动调用 第一个参数 this 要指向的对象,后面是 ...

  2. JS 开发中数组常用的方法

    大家有没有想过,js数组为什么会有这么多的方法,没错,就是为了不同场景下处理数据的需要,就像设计模式一样,都是为了能更好的处理当前场景的需要. 首先怎么创建一个数组呢, // 两种方式 // 1,构造 ...

  3. spoj2 prime1 (区间筛)

    给定t组询问,每组询问包括一个l和r,要求\([l,r]\)的素数有哪些 其中\(t \le 10,1 \le l \le r \le 1000000000 , r-l \le 100000\) Qw ...

  4. Java中的基本类型和包装类

    Java中基本数据类型与包装类型有   基本类型     包装器类型   boolean Boolean char Character int Integer byte Byte short Shor ...

  5. springboot 事务执行全流程分析

    springboot 事务执行全流程分析 目录 springboot 事务执行全流程分析 1. 事务方法执行前的准备工作 2. 业务代码的调用 3. 事务方法执行后处理 4. 业务代码在事务和非事务中 ...

  6. js--Symbol 符号基本数据类型

    前言 ECMAScript 6 中新增了 Symbol 符号这一基本数据类型,那么Symbol 是用来干什么的,对开发又有什么帮助呢?本文来总结记录一下 Symbol 的相关知识点. 正文 Symbo ...

  7. centos7 配置ftp服务器搭建(匿名访问,以及本地登录)

    大家好,今天来给大家分享一个基于centos 7的ftp服务器搭建 实现功能:匿名访问,本地登录 查看系统版本: [root@localhost ~]# cat /etc/redhat-release ...

  8. Noip模拟57 2021.9.20

    规律总结:联考必爆炸 T1 2A 没$A$掉的大水题,但是是真的不知道$000$前面的$00$也算先导$0$,以后要长记性,这种东西不能再错了 再打三遍: $000$前面的$00$也算先导$0$ $0 ...

  9. 因为一个小小的Integer问题导致阿里一面没过,遗憾!

    面试题:new Integer(112)和Integer.valueOf(112)的区别 面试官考察点猜想 这道题,考察的是对Integer这个对象原理的理解,关于这道题的变体有很多,我们会一一进行分 ...

  10. GPS与AGPS定位服务

    最近客户反馈车子启动从车库开到地面后,机器定位相对OBD内部定位会慢很多. 机器定位主要依赖定位模块 + AGPS辅助定位. 其中定位模块目前主流支持的有以下三种定位系统. 一.GPS(全球定位系统) ...