react hooks(重中之重)

Hooks 介绍

Hook 简介 – React

react hooks是v16.8新增的特性, 他允许你 在不写类组件(即:函数式组件)的情况下操作state 和react的其他特性(如:生命周期的钩子函数)。

hooks 只是多了一种写组件的方法,使编写一个组件更简单更方便,同时可以自定义hook把公共的逻辑提取出来,让逻辑在多个组件之间共享。

Hook 是什么? Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。

函数式组件里面没有state,所以,无状态组件我们用函数写,或者说函数式组件是无状态组件。而现在有了Hook后,函数式组件里,也可以使用state了。当然还有其它Hook。

使用规则

  • Hook可让您在不编写类(组件)的情况下使用状态(state)和其他React功能

  • 只能在顶层调用Hooks 。不要在循环,条件或嵌套函数中调用Hook

  • 只能在functional component或者自定义钩子中使用Hooks

  • 钩子在类内部不起作用,没有计划从React中删除类

useState (使用状态):

格式:

1、定义状态:
const [状态名,更新状态的函数] = React.useState(初始值|函数);

如:
1)、基本类型的状态
声明一个新的叫做 “count” 的 state 变量,初始值为0 。

const [count, setCount] = React.useState(0); //useState函数返回的是数组


相当于类组件中的
this.state={
    count :0
}

2)、引用类型的状态
const [person, setPerson] = React.useState({name: '张三疯', age: 18,sex:"女"})
const [person, setPerson] = React.useState(() => ({name: '张三疯', age: 18,sex:"女"}))

2、读取值:
{count}
{person.name}   {person.age}

3、修改值:  
  setCount(5);

  //对于引用类型,不能局部更新(即:不能只改某个属性),所以,需要使用扩展运算符先拷贝以前所有的属性
  setPerson({
     ...person, //拷贝之前的所有属性
     age:person.age+1,
     name: '张四疯' //这里的name覆盖之前的name
 })

注意:

首先,需要知道,函数式组件重新渲染时,会执行函数里的所有代码,

那么,当函数式组件重新渲染时,会不会再次把状态的值恢复成初始值呢?答案是:不会。后续组件重新渲染时,会使用最后一次更新的状态值

【官网解释: React 会确保 setState 函数的标识是稳定的,并且不会在组件重新渲染时发生变化 】

示例代码:

import React,{useState} from 'react';

function App() {
// 声明一个叫 "count" 的 state 变量
  const [count,setCount] = useState(0); //在App组件重新后,useState 返回的第一个值将始终是更新后最新的 count。

  return (
    <div className="App">
      <p>{count}</p>
      <input type="button" value="测试" onClick={()=>{setCount(count+1)}} />
    </div>
  );
}

对应的函数class组件:

class App extends React.Component {
  state = {
      count:0
  }
  render = () => (
    <div>
       <p>{this.state.count}</p>
       <input type="button" value="测试" 
			onClick={()=>this.setState({count:this.state.count+1})} />
    </div>
  )
}

我们之前把函数式的组件叫做“无状态组件”。但现在我们为它们引入了使用 React state 的能力

再如:


function App() {
  const [person, setPerson] = React.useState({name: '张三疯', age: 18})
 
  const onClick = () =>{
    //setPerson不可以局部更新,如果只改变其中一个,那么整个数据都会被覆盖,所以,需要使用扩展运算符先拷贝以前所有的属性
    setPerson({
        ...person, //拷贝之前的所有属性
        age:person.age+1,
        name: '张四疯' //这里的name覆盖之前的name
    })
  }

  return (
    <div className="App">
        <p>name:{person.name}</p>
        <p>age:{person.age}</p>
        <input type="button"  value="测试" onClick={onClick} />
    </div>
  );
}

useEffect 处理副作用 (生命周期)

可以使得你在函数组件中执行一些带有副作用的方法,天哪,“副作用”(大脑中无数个????)。

每当 React组件更新之后,就会触发 useEffect,在第一次的render 和每次 update 后的useEffect触发,不用再去考虑“初次挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

我们在函数式组件里,没有 componentDidMountcomponentDidUpdatecomponentWillUnmount,用useEffect。即:当数据发生变化后,渲染到组件上,组件渲染完毕后,就会调用useEffect。

格式:

useEffect(回调函数,[依赖]); //在render之后触发useEffect,进一步调用回调函数

1、useEffect的无条件执行(只有一个参数)

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

function App() {
  const [count,setCount] = useState(0);
  
  //useEffect:相当于 componentDidMount,componentDidUpdate
  useEffect(()=>{
      console.log("userEffect");
      document.title = count;
  });

  return (
    <div className="App">
      <p>{count}</p>
      <input type="button" value="测试" onClick={()=>{setCount(count+1)}} />
    </div>
  );
}

2、useEffect的条件执行(useEffect的第二个参数)

当useEffect只有一个参数时,会无条件执行,但是,当发送请求时(页面的初始数据来自后端),一旦把请求放在useEffect里,就会无休止的执行。因为,当请求的数据回来后,引起组件的更新,组件更新后,再次触发useEffect,再次发送请求,再次组件更新………………,陷入到了无限的死循环。那么,可以使用useEffect的第二个参数。

第二个参数表示,useEffect是否再次触发,依赖于某个值。当为空数组时,表示不会二次触发(因为,没有任何依赖)。即:componentDidMount时会触发,componentDidUpdate不会触发。

如下代码,由于依赖是空,所以,useEffect只表示componentDidMount。

 useEffect( async ()=>{
      let data = await getBooks();  //发送请求的代码已经封装     
      setBooks(data);      
  },[]);

如下代码,表示componentDidMount,和 count变化后引起的componentDidUpdate。

 

 useEffect( async ()=>{
      let data = await getBooks();  //发送请求的代码已经封装     
      setBooks(data);      
  },[count]);

useContext(使用状态树传参)

Context状态树的使用,比较复杂,特别是使用Consumer时。

useContext这个hook能让Context使用起来变得非常简单。不需要再使用Consumer。使用useContext就能拿到context状态树里的值。

const value = useContext(context对象);

useContext函数需要传入一个 context 对象(React.createContext 的返回值),返回该 context 的当前值----由上层组件中距离当前组件最近的 的 value prop 决定。

当组件上层最近的 Provider 更新时,该 Hook 会触发重新渲染 。

示例:

//context/index.js

import {createContext} from "react";

export default createContext({count:0});

//主入口文件 index.js

import ReactDOM from 'react-dom';
import App from './App';
import Context from "./context"

let count = 10;

ReactDOM.render(
    <Context.Provider value={count}>
     <App/>
    </Context.Provider>,
  document.getElementById('root')
);


//孙子组件 App-->User-->UserAdd.js

import myContext from "../../../context";
import {useContext} from "react";

export default (props)=>{
    let context = useContext(myContext);
    console.log(context);
    return (
        <div>
            <h1>我是用户添加页面</h1>
            <p>count:{context}</p> //此处使用比起consumer是不是简单的多得多得多了呢?
			<p></p>
        </div>
    );
}

useCallBack

一、概念和作用

1、memo高阶函数:

memo解决的是函数式组件的无效渲染问题,当函数式组件重新渲染时,会先判断数据是否发生了变化。相当于类组件的PureComponent(默认提供ShouldComponentUpdate)

2、useCallback:

1)、useCallback会返回一个函数的memoized(记忆的)值

2)、在依赖不变的情况下,多次定义(如:函数)的时候,返回的值是相同的 。

3)、格式:

let 新的函数 = useCallback(曾经的函数, [依赖的值]) 

二、使用场景:

1、memo高阶函数的使用场景:

不论父组件是什么类型的组件,子组件是否渲染 :

1)、 如果子组件是类组件(继承自PureComponent)

那么是否渲染由props和state是否改变决定;

2)、如果子组件是函数式组件

只要父组件渲染,子组件会无条件渲染。如下是代码示例:

//父组件:

import { useState } from "react";
import SonFn from "./SonFn";

export default () => {
    console.log("父组件");
    const [count, setCount] = useState(1);

    let changeCount = () => {
        setCount(count + 1);
    }

    return (
        <>
            <h1>useCallback</h1>
            <p>{count}</p>
            <input type="button" value="修改count" onClick={changeCount} />
            <hr />
            <SonFn />
        </>
    )
}

//子组件:
./SonFn.js

export default ()=>{
    console.log("子组件");
    return (
        <div>
            <h5>子组件(函数式组件)</h5>
        </div>
    )
}   

只要点击按钮"修改count”,父组件就会刷新,而子组件SonFn也会无条件渲染(这是无效的渲染)。

3)、解决方案:

把子组件用高阶函数memo进行包裹,就能解决子组件的无条件渲染问题,即:子组件的渲染就会由props和state决定,有点像类组件继承自PureComponent的感觉。

如下是代码(只需要把子组件的代码进行修改就行):

//子组件:
import React,{memo} from 'react'

const SonFn = ()=>{
    console.log("子组件");
    return (
        <div>
            <h5>子组件(函数式组件)</h5>
        </div>
    )
}

export default memo(SonFn);

2、useCallback的使用场景:

父组件是函数式组件,子组件也是函数式组件(并且用memo包裹)

1)、子组件的属性是数据:

如果数据不变化,那么子组件不渲染,如果数据发生变化,那么子组件渲染。这里就没有性能问题。

//父组件

import { useState,useCallback } from "react";
import SonFn from "./SonFn";

export default () => {
    console.log("父组件UseCallback");
    const [count, setCount] = useState(1);

    let changeCount = () => {
        setCount(count + 1);
    }

    return (
        <>
            <h1>useCallback1</h1>
            <p>{count}</p>
            <input type="button" value="修改count" onClick={changeCount} />
            <hr/>
        	{/*此处给子组件传入了数据count,count只要发生变化,子组件就会重新渲染*/}
            <SonFn count={count} />
        </>
    )
}

//子组件:
./SonFn.js

import React,{memo} from 'react'

const SonFn = ({count})=>{
    console.log("子组件");
    return (
        <div>
            <h5>子组件(函数式组件)</h5>
            <p>{count}</p>
        </div>
    )
}

export default memo(SonFn);

2)、子组件的属性是函数时,就会出现问题:

父组件刷新了,子组件依然会刷新。因为,父组件(函数式)每次刷新时,函数都会重新定义,那么传给子组件的函数属性必然会发生变化。所以子组件会刷新,

如下是示例代码:

//父组件:

import { useState } from "react";
import SonFn from "./SonFn";

export default () => {
    console.log("父组件");
    const [count, setCount] = useState(1);

    let changeCount = () => {
        setCount(count + 1);
    }

    let increment = ()=>{
        console.log("increment");
    }

    return (
        <>
            <h1>useCallback</h1>
            <p>{count}</p>
            <input type="button" value="修改count" onClick={changeCount} />
            <hr/>
            <SonFn onMyClick={increment} />
        </>
    )
}

//子组件:
./SonFn.js

import React,{memo} from 'react'

const SonFn = ()=>{
    console.log("子组件");
    return (
        <div>
            <h5>子组件(函数式组件)</h5>
        </div>
    )
}

export default memo(SonFn);

3)、解决方案:把传给子组件的函数属性,用useCallback包裹。

格式:

let 新的函数 = useCallback(曾经的函数, [依赖的值]) 

如下是修改后的代码(只需要修改父组件的代码):

以下代码把increment函数进行了包裹

export default () => {
    console.log("父组件");
    const [count, setCount] = useState(1);

    let changeCount = () => {
        setCount(count + 1);
    }

    let increment = useCallback(()=>{
        console.log("increment");
    },[]) // 该函数永远不会重新定义(没有依赖)
    
    /*
    let increment = useCallback(()=>{
        console.log("increment");
    },[count]) // 当count的值发生变化是,该函数才会重新定义
	*/
    
    return (
        <>
            <h1>useCallback</h1>
            <p>{count}</p>
            <input type="button" value="修改count" onClick={changeCount} />
            <hr/>
            <SonFn onMyClick={increment} />
        </>
    )
}

三、总结:

1、“万恶之源” :函数式组件每次重新渲染时,都会把函数体里的所有代码执行一遍。

2、useCallback解决的是 防止函数式组件里的 子函数(闭包) 多次被定义。既就是:useCallback是保证函数式组件重新渲染时,组件里的函数(闭包)只被定义一次

useCallback 记忆函数(详细版的)

react hooks系列_useCallback_田江的博客-CSDN博客_hooks usecallback

useMemo 记忆组件

 //格式
  useMemo(函数,数组);

  //意思:
  // 当数组中的其中一个元素,发生变化时,就会调用 函数 。

如: const nameStr = useMemo(()=>genName(name),[name])

表示,当name发生变化时,才会调用 ()=>genName(name)函数

如: const nameStr = useMemo(()=>genName(name),[name,age])

表示,当name或者age发生变化时,都会调用 ()=>genName(name)函数

以下代码中,如果不使用useMemo,当我们点击“修改年龄”的按钮时,也调用了函数genName()。这其实是性能的损耗。

import React,{useState,useMemo} from "react";
import './App.css';


function Person({ name, age}) {
    
  console.log("Person函数");
    
  function genName(name) {
    console.log('genName')
    return '姓名:'+name;
  }

  let nameStr = genName(name);  //没有使用useMemo
 // const nameStr =  useMemo(()=>genName(name),[name]) //此处使用  useMemo

  return (
    <>
      <div>{nameStr}</div>
      <hr/>
      <div>年龄:{age}</div>
    </>

  )
}


function App() {
  const [name, setName] = useState('张三疯')
  const [age, setAge] = useState(12)

  return (
    <>
      <button onClick={() => setName("姓名"+age)}>修改姓名</button>
      <button onClick={() => setAge(age+1)}>修改年龄</button>
      <hr/>
      <Person name={name} age={age}></Person>
    </>
  )
}

export default App;

useMemo:解决的是:防止无效函数调用

useCallback:解决的是:防止无效函数定义

useRef 保存引用值

https://reactjs.bootcss.com/docs/hooks-reference.html#useref

useRef 返回一个可变的 ref 对象,其(ref 对象) .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

import {useRef} from "react";

let refContainer = useRef(initialValue)  

<JSX ref={refContainer} ...

refContainer.current.dom操作

一个常见的用例便是命令式地访问子组件:

function TextInputWithFocusButton() {
    //定义了一个ref变量:inputEl
  const inputEl = useRef(null);
  
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
    
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

扩展:

https://zhuanlan.zhihu.com/p/430825777

为什么react选择了函数式组件(剖析原理)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值