Hooks学习

172 阅读14分钟

你已经准备好了,让我们先大致了解一下 React 吧!

React 是什么?

React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。

咱们再学习组件之前先了解一下,基础知识JSX

知识点扩展-JSX

jsx 表达式

import '../assets/Apptest.css';
 const Sjsx = () =>{
    const styleObj = {
        color: '#000000'
    }
    const listonstyle = {
        listStyle: 'none'
    }
   const name =  '柴柴'
   const Elem = <h2>您好,我叫,{name}</h2> // 只能执行最后一项
   return (
     {name}
     {Elem}
   )
 }
 // 您好我叫,柴柴

jsx 列表渲染

 const Sjsx = () =>{
     const styleObj = {
        color: '#000000'
    }
    const listonstyle = {
        listStyle: 'none'
    }
     // JSX列表渲染
    const songs = [
        { id: 1, name: '我是列表1' },
        { id: 2, name: '我是列表2' },
        { id: 3, name: '我是列表3' }
    ]
   return (
   <ul>{songs.map(item => 
       <li style={listonstyle} className='title' key={item.id}>
       {item.name}
       </li>)}
   </ul>
   )
 }
 // 我是列表1,我是列表2,我是列表3

jsx 条件渲染

 const Sjsx = () =>{
     // JSX列表渲染
       const flag = true
       return (
           {flag ? 'react真有趣':'vue真有趣'}
           {flag ? <span>this is span</span> : null},
          )
 }
 //react真有趣,this is span

JSX样式处理

 const Sjsx = () =>{
   return (
      <div style={{ color: 'green' }}>
      this is a div
      </div>
      )
 }
 

行内样式 - style - 更优写法 *

  const styleObj = {
       color: '#000000'
   }
    const Sjsx = () =>{
      return (
          <div style={styleObj}>
               <span>JSX样式处理行内样式</span>
           </div>
         )
    }

字符串、数值、布尔值、null、undefined、object([] / {} ) 1 + 2、'abc'.split('')、['a', 'b'].join('-'),fn()if 语句/ switch-case 语句/ 变量声明语句,这些叫做语句,不是表达式,不能出现在 {} 中!!

类组件

state

//  类组件模板
  import  React,{Component} from 'react'
  
    class StateClass extends  Component {
     state = {
     
     } // 初始值
     this.setState((props,state)=>{
     
     }) 
     onchangeHandle = () =>{
       /***
       
       */
      this.setstate((props,state)=>{
     
     }) 
     
     }
     
    }
    
  -----state的使用------
  class Childs extends Component {
   state = {
       num: '我是子组件的值'
        // Boolean Array Object,String,Number
   }
   onHandleChange = () => {
       this.setState((v, p)=>{
           return {
                 // Boolean Array Object,String,Number
               num: p.num, //  获取到最新的值
               //                 name:props.name,
               //                 sex:props.sex
           }
       })
   }
   render() {
       return (
           <div>
               {this.state.num}
               <button onClick={this.onHandleChange}>变化</button>
           </div>
       )
   }
}
class App extends Component {
   render() {
       return (
           <Childs num="他说他是个男孩" />
       )
   }
}
export default App
  
//      我是子组件的值 点击按钮后 变成 他说他是个男孩


props props:属性,是一个 JavaScript 对象。

  • props 是调用方法传递给组件的数据(类似于函数的形参), 而state是在组件内被组件自己管理的数据(类似于在一个函数内声明的变量) -props不可修改的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改,由于 props 是传入的,并且它们不能更改,
//  类组件模板
    
  -----props的使用------
  class Childs extends Component {
   state = {
       num: '我是子组件的值'
        // Boolean Array Object,String,Number
   }
    
   render() {
    const { name, age, sex } = this.props;
    return (
     <ul>
       <li>姓名:{name}</li>
       <li>性别:{sex}</li>
       <li>年龄:{age}</li>
     </ul>
   );
   }
}
class App extends Component {
   render() {
       return (
       // Boolean Array Object,String,Number
           <Childs name="jerry" age="19" sex="男"  />
       )
   }
}
export default App
  
//      姓名:jerry,性别:男,age:19


补充知识点

let arr1 = [1, 3, 5, 7, 9];
let arr2 = [2, 4, 6, 8, 10];
console.log('展开数组', ...arr1); // 展开数组
let arr3 = [arr1, arr2]; // 数组合并
console.log('数组合并', arr3);
let copyArr = [...arr1]; // 数组复制
console.log('数组复制', copyArr);

// 函数实参
function log(x, y, z) {
 console.log('函数实参', x, y, z)
}
let args = [1, 1, 1, 1, 1];
log(...args);

// 函数行参
function sum(...numbers) {
return numbers.reduce((pre, cur) => {
  return pre + cur;
})
}
console.log('函数行参', sum(1, 2, 3, 4));

let obj1 = {a: 1, b: 2};
let obj2 = {c: 3, d: 4};
let obj3 = {...obj1, ...obj2}; // 对象合并
console.log('对象合并', obj3);
let copyObj = {...obj1}; // 对象复制
console.log('对象复制', copyObj);

let person = {name: 'tom', aget: '18'};
console.log('对象展开', ...person); // 对象展开失败

补充:props和state区别:

  • props和state都是用来存储数据的
    • props存储的是父组件传递归来的数据

    • state存储的是自己的数据

    • props只读的

    • state可读可写

生命周期旧(旧)

在 React 中,对于每一次由状态改变导致页面视图的改变,都会经历两个阶段:render 阶段commit 阶段

只有 class 组件才有生命周期,因为 class 组件会创建对应的实例,而函数组件不会。组件实例从被创建到被销毁的过程称为组件的生命周期

image.png

三个阶段

  1. 初始化(挂载)阶段: 由 ReactDOM.render() 触发——初次渲染

    1. constuctor()

    2. componentWillMount()

    3. render()=======> 必须使用的一个

    4. componentDidMount()  =====> 常用

    一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

  2. 更新阶段: 由组件内部 this.setSate() 或 父组件render 触发

    1. shouldComponentUpdate()

    2. componentWillUpdate()

    3. render()=======> 必须使用的一个

    4. cmponentDidUpdate() 18更新后:

  3. 卸载组件由:root.unmount()* 触发

    1. componentWillUnmount()  =====> 常用

    一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

    componentReceiveProps:如果父组件导致组件重新渲染,即使props没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较。

    因此,componentReceiveProps并不是由props的变化触发的,而是由父组件的更新触发的

操作实例: 利用求和梳理一遍整个生命周期的执行顺序

import React,{Component} from 'react'
import ReactDOM from 'react-dom'
// 创建子组件
class Count extends Component {
    // 构造器----忽略
    state = {
        // 创建初始化参数
        count: 0,
    }
    addHandle = () => {
        //增加的逻辑处理
        // 获取原状态
        const { count } = this.state
        // 更新状态
        this.setState((props, state) => {
            return {
                count: count + 1
            }
        })
    }
    deathHandle = () => {
        // 卸载的逻辑处理
        root.unmount(document.getElementById('root'))
    }
    force = () => {
        // 强制更新组件
        this.forceUpdate()
    }

    /***
     组件将要挂载的钩子
     componentWillMount ====> 改用UNSAFE_componentWillMount
    **/
    componentWillMount() {
        console.log("count =====>componentWillMount")
    }
    /***
      组件挂载完毕的钩子
     **/
    componentDidMount() {
        console.log("count =====>componentDidMount")
    }
    /***
     组件将要卸载的钩子
    **/
    componentWillUnmount() {
        console.log("count =====>componentWillUnmount")
    }
    /**
     控制组件更新的“阀门”
    **/
    shouldComponentUpdate() {
        console.log('Count =====> shouldComponentUpdate');
        return true
    }
    /**
  组件将要更新的钩子
  **/
    componentWillUpdate() {
        console.log("Count ====>componentWillUpdate")
    }
    /***
     组件更新完毕的钩子
    **/
    componentDidUpdate() {
        console.log('Count--- componentDidUpdate');
    }
    render() {
        console.log('Count---render');
        // 渲染dom
        const { count } = this.state

        return (
            <div>
                <h2>当前求和为:{count}</h2>
                <button onClick={this.addHandle}>新增</button>
                <button onClick={this.deathHandle}>卸载组件</button>
                <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
            </div>
        )

    }

}

/****
 * 执行顺序:
 * ---- 初始化阶段------
 * count =====>componentWillMount
 * Count---render
 * count---componentDidMount
 * count =====>componentWillUnmount
 * 
 * 
 * ---- 更新阶段-----
 * Count =====> shouldComponentUpdate
 * Count ====>componentWillUpdate
 * Count---render
 * Count--- componentDidUpdate
 * 
 * --- 卸载组件-----
 * count =====>componentWillUnmount
 * 
 * 
 * 
 */

// 挂载

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <React.StrictMode>
        <Count />,
    <App />
    </React.StrictMode>
);

image.png

出现新的生命周期缘由

旧的生命周期十分完整,基本可以捕捉到组件更新的每一个state/props/ref,没有什从逻辑上的毛病。但是官方react打算在17版本推出新的Async Rendering,提出一种可被打断的生命周期,而可以被打断的阶段正是实际dom挂载之前的虚拟dom构建阶段,也就是要被去掉的三个生命周期

生命周期一旦被打断,下次恢复的时候又会再跑一次之前的生命周期,

  • 标记为不安全: componentWillMount(), componentWillReceiveProps(), componentWillUpdate

现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用

  • 新的: static getDerivedStateFromProps, getSnapshotBeforeUpdate

生命周期的三个阶段(新)

image.png

三个阶段:

1.初始化(挂载)阶段: 由 ReactDOM.render() 触发—初次渲染

  1. constructor()

  2. getDerivedStateFromProps

  3. render() =====> 必须使用的一个

  4. componentDidMount() =====> 常用

一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

2.更新阶段: 由组件内部**this.setSate()**或父组件重新 render 触发

  1. getDerivedStateFromProps,

  2. shouldComponentUpdate(),

  3. render() =====> 必须使用的一个,

  4. getSnapshotBeforeUpdate,

  5. componentDidUpdate(),

18更新后:

  1. 卸载组件: 由 root.unmount() 触发

  2. componentWillUnmount() =====> 常用

一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

import React, { StrictMode, Component } from "react";

import { createRoot } from "react-dom/client";

\


import App from "./App";

//创建组件

class Count extends Component {

//构造器

//初始化状态

state = { count: 0 };

//加1按钮的回调

add = () => {

//获取原状态

const { count } = this.state;

//更新状态

this.setState({ count: count + 1 });

};

//卸载组件按钮的回调

death = () => {

root.unmount(document.getElementById("test"));

};

//强制更新按钮的回调

force = () => {

this.forceUpdate();

};

//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps

static getDerivedStateFromProps(props, state) {

console.log("getDerivedStateFromProps", props, state);

return null;

}

//在更新之前获取快照

getSnapshotBeforeUpdate() {

console.log("getSnapshotBeforeUpdate");

return "atguigu";

}

//组件挂载完毕的钩子

componentDidMount() {

console.log("Count---componentDidMount");

}

//组件将要卸载的钩子

componentWillUnmount() {

console.log("Count---componentWillUnmount");

}

//控制组件更新的“阀门”

shouldComponentUpdate() {

console.log("Count---shouldComponentUpdate");

return true;

}

//组件更新完毕的钩子

componentDidUpdate(preProps, preState, snapshotValue) {

console.log(

"Count---componentDidUpdate",

preProps,

preState,

snapshotValue

);

}

/****

* 执行顺序:

* ---- 初始化阶段------

* count =====>getDerivedStateFromProps {count: 199} {count: 0}

* Count---render

* count---componentDidMount

* count =====>componentWillUnmount

* count---componentDidMount

*

*

* ---- 更新阶段-----

* ---- 强制更新组件---

* Count =====> getDerivedStateFromProps

* Count ====>shouldComponentUpdate

* Count---render

* Count --- getSnapshotBeforeUpdate

* Count--- componentDidUpdate

*

* --- 卸载组件-----

* count =====>componentWillUnmount

*

*

*

*/

render() {

console.log("Count---render");

const { count } = this.state;

return (

<div>

<h2>当前求和为:{count}</h2>

<button onClick={this.add}>点我+1</button>

<button onClick={this.death}>卸载组件</button>

<button onClick={this.force}>

不更改任何状态中的数据,强制更新一下

</button>

</div>

);

}

}

\


//渲染组件

\


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

const root = createRoot(rootElement);

\


root.render(

<StrictMode>

<Count count={199} />

<App />

</StrictMode>

);

具体方法详解

1、constructor(props): 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前 调用 super(props)。 否则,this.props 在构造函数中可能会出现未定义的 bug。

通常,在 React 中,构造函数仅用于以下两种情况:

通过给 this.state 赋值对象来初始化内部 state 为事件处理函数绑定实例 在 constructor() 函数中不要调用 setState() 方法。如果组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始化state

2、render(): 是 class 组件中唯一必须实现的方法

render 被调用时,它会检查 this.propsthis.state 的变化并返回以下类型之一:

fragments 的知识点的补充

class App extends React.Component {
  state = {
    items: [
      {
        id: '`2`',
        name: '计算机',
        description: '用来计算的仪器...'
      },
      {
        id: '2',
        name: '显示器',
        description: '以视觉方式显示信息的装置...'
      }
    ]
  }

  render() {
    return <Glossary items={this.state.items} ></Glossary>
  }
}

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // 没有 `key`,React 会发出一个关键警告
        <React.Fragment key={item.id}>
          <dt>{item.name}</dt>
          <dd>{item.description}</dd>
        </React.Fragment>
      ))}
    </dl>
  )
}

React 元素。通常通过 JSX 创建。例如,<div/> 会被 React 渲染为 DOM 节点, 会被 React 渲染为自定义组件,无论是<div /> 还是 均为 React 元素。 数组或fragments。 使得 render 方法可以返回多个元素。欲了解更多详细信息,请参阅fragments 文档。 Portals。可以渲染子节点到不同的 DOM 子树中。欲了解更多详细信息,请参阅有关 portals 的文档 字符串或数值类型。它们在DOM 中会被渲染为文本节点 布尔类型或 null。什么都不渲染。(主要用于支持返回 test && <Child /> 的模式,其中test 为布尔类型) 注意: render() 函数应该为纯函数,不进行实际上的渲染动作,它只是一个JSX描述的结构,最终是由React来进行渲染过程。

render函数不应该有任何操作,对页面的描述完全取决于this.props 和 this.state返回的结果。这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。可以理解为: UI = render(data)

如需与浏览器进行交互,请在componentDidMount() 或 其他生命周期方法 中执行你的操作。保持 render() 为纯函数,可以使组件更容易使用、维护。

3、componentDidMount(): 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。

如需通过网络请求获取数据,此处是实例化请求的好地方

这个方法也是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅

可以在 componentDidMount() 里直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。

注意: 请谨慎使用该模式,因为它会导致性能问题。通常,应该在 constructor() 中初始化 state。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,可以使用此方式处理

4、componentDidUpdate(prevProps, prevState, snapshot): 会在更新后会被立即调用。首次渲染不会执行此方法。当组件更新后,可以在此处对 DOM 进行操作。

如果对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。

也可以在 componentDidUpdate() 中直接调用 setState(),但请注意它必须被包裹在一个条件语句里,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。

5、componentWillUnmount(): 会在组件卸载及销毁之前直接调用。

在此方法中执行必要的 清理操作,例如,清除timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

this 问题说明

你必须谨慎对待 JSX 回调函数中的this, 在javascript中,class 的方法 默认不会绑定this, 如果你忘记绑定 this.handleClick 并把它传入了onClick,当你调用了这个函数的时候 this的值为undefined。

这并不是React特有的行为;这其实与JavaScript函数工作原理有关。通常情况下,如果你没有在方法后面添加() ,例如onClick={this.handleClick} 你应该为这个方法绑定 this 这里我们作为了解内容,随着js标准的发展,主流的写法已经变成了class fields,无需考虑太多this问题

函数组件

能够独立使用函数完成react组件的创建和渲染

react 组件得本质

Modal => View 得映射,其中 Modal 对应 react 中的 state + props。

react 通过 JSX 将 Modal 与 View 进行 数据绑定

image.png

概念

使用 JS 的函数(或箭头函数)创建的组件,就叫做函数组件

  • 组件的定义
  • 组件的state状态定义,组件的状态获取,组件的绑定
  • 组件的setstate状态的修改
  • 组件的使用props

声明: 对于函数组件(Function Component)来说,它没有 class 组件中的 componentDidMount、componentDidUpdate 等生命周期方法,也没有 State,但这些可以通过 React Hook 实现。

现在我们引入一个新的概念:Hooks,顾名思义什么是Hooks, 英文理解: 钩子 就是我们需要什么钩子,通过Hooks 把需要的钩出来实现显示

image.png

Hooks 约定和规则

在深入研究各种钩子之前,看看适用于它们的约定和规则可能会有所帮助。以下是适用于钩子的一些规则。

  1. 挂钩的命名约定应以前缀开头use。所以,我们可以有useState,useEffect等等。如果你使用像 Atom 和 VSCode 这样的现代代码编辑器,ESLint 插件对于 React 钩子来说可能是一个非常有用的功能。该插件提供了有关最佳实践的有用警告和提示。
  2. 钩子必须在组件的顶层调用,在 return 语句之前。它们不能在条件语句、循环或嵌套函数中调用。
  3. 必须从 React 函数(在 React 组件或另一个钩子内部)调用钩子。不应从 Vanilla JS 函数调用它。 我会在下面标注清楚那些需要在项目中使用,哪些只用来了解
  • useState()
  • useEffect()
  • useContext()
  • useReducer()
  • useRef()
  • useCallback()
  • useMemo()
  • useImperativeHandle()
  • useLayoutEffect()
  • useDebugValue()
  • useDeferredValue()
  • useTransition()
  • useId()
  • useSyncExternalStore()
  • useInsertionEffect()

useState钩子

useState 钩子是最基本和最有用的React 钩子, 就像其他内置的钩子一样,这个钩子必须被导入React 才能在我们的应用程序中使用

import {useState} from 'react'

要初始化状态,我们必须声明状态及其更新函数并传递一个初始值。 const [state, updaterFn] = useState()

const [number,setNumber] =  useState(0) // Number
const [state, updaterFn] = useState('hello,hooks') // String
const [nos, setnos] = useState(true) // Boolean
const [data, settdata] = useState({name: 'kkb', age: 10}) // object
const [Arraydata, setArraydata] = useState(['FromData', 'slcasss']) // Array
const [objArray, setobjArray] = useState([{id: 1, name: 'vue'},
 {id: 2, name: 'react'},
 {id: 3, name: 'angalur'}]) // ArrayObject

useState => Number的实例

import { useState } from "react";

import { Button } from "antd";

const App = (props) => {

const [number, setNumber] = useState(0);

const inHandleButton = () =>{

setNumber(number + 1)

}

return (

<div>

{/* 直接拿到 初始化的值 number :0*/}

<h1>{number}</h1>

{/* 通过事件改变 的值 */}

<Button type="primary" onClick={inHandleButton}>Change</Button>

</div>

);

};

export default App;

useState => String实例

import { useState } from "react";

import { Button } from "antd";


const App = (props) => {

const [num, setNum] = useState("在忙吗?");

const inHandleButton = () =>{

setNum(number + '买菜')

setNum('在忙')

}

return (

<div>

{/* 直接拿到 初始化的值 num :0*/}

<h1>{num}</h1>

{/* 通过事件改变 的值 */}

<Button type="primary" onClick={inHandleButton}>Change</Button>

</div>

);

};

export default App;

useState => Boolean 实例



/***
 * useEffect
 * @type {string}
 * useEffect 其实就是让函数组件拥有了生命周期。
 * 传给 useEffect 的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用。
 * 这使得它适用于许多常见的副作用场景,比如
 * 设置订阅和事件处理
 * 等情况,
 * 因为绝大多数操作不应阻塞浏览器对屏幕的更新。
 * 结合 useEffect 来实现 useState() // Boolean 场景的使用
 */
 /** 
 * const [data,setData] = useState()

 useEffect(()=>{
    const fetchData = async() => {
        const res = await fetchNewData()
        setData(res.data)
    }
    fetchData()
},[data])
 知识扩展:
 每一个useEffect中的props和state都是独立的,不是state在不变的effect中变化,
 而是每一个effect中有不变的state

 */