IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> react中的hooks -> 正文阅读

[JavaScript知识库]react中的hooks

react?hooks

Hooks?介绍

https://zh-hans.reactjs.org/docs/hooks-intro.html

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

类组件有state,函数式组件没有state,但是,我们又要用。。。。。

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(初始值|函数);
//?如:
//?声明一个新的叫做?“count”?的?state?变量,初始值为0?。
const?[count,?setCount]?=?React.useState(0);?//useState函数返回的是数组
//?相当于类组件中的
this.state={
????count?:0
}
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?函数的标识是稳定的,并且不会在组件重新渲染时发生变化?】
示例代码:

//useState--数据是基本类型
//?1.引入react,解构useState
import?React,{useState}?from?'react';
//?函数式组件
function?App()?{
??//定义状态
//?声明一个叫?"count"?的?state?变量
??const?[count,setCount]?=?useState(0);?//在App组件重新后,useState?返回的第一个值将始终是更新后最新的?count。
//?return出jsx?
??return?(
????<div?className="App">
??????<p>count:{count}</p>
??????<input?type="button"?value="测试"?onClick={()=>{setCount(count+1)}}?/>
????</div>
????//1--不能直接赋值{setCount(count+1)}}-- 需要用箭头函数--如果直接赋值,就会是一个无限循环
??);
  //2--不能直接定义一个const   let const=0,需要钩一下useState 进行渲染,直接定义并不是不能改变,但一定不能渲染,
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DdGPScZP-1628263619454)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1628061449964.png)]

对应的函数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?的能力?
再如:(文字版)

//useState--数据是对象--引用类型
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?后的render触发,不用再去考虑“初次挂载”还是“更新”。React?保证了每次运行?effect?的同时,DOM?都已经更新完毕。

组件更新后想干点啥,就写再useEffect,不用考虑—更新

你可以把?useEffect?Hook?看做?componentDidMountcomponentDidUpdate—渲染完毕做的事 和?componentWillUnmount?这三个函数的组合。
??????????我们在函数式组件里,没有?componentDidMountcomponentDidUpdate?和?componentWillUnmount,用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(()=>{},[count])?//表示count的值发生变化才会触发useEffect。

如:useEffect(()=>{},[])?//表示useEffect不依赖任何值,即:不会有二次触发(componentDidUpdate)。
????????????
???????如下代码,由于依赖是空,所以,useEffect只表示componentDidMount。

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

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

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

useRef?保存引用值

https://reactjs.bootcss.com/docs/hooks-reference.html#useref?
??useRef?返回一个可变的?ref?对象,其(ref?对象)?.current?属性被初始化为传入的参数(initialValue)。返回的?ref?对象在组件的整个生命周期内保持不变。

hooks中可以通过 useRef()获取Dom节点

//引入 解构 声名 输出渲染
import?{useRef}?from?"react";
//可变的?ref?对象  其(ref?对象)?`.current`?属性被初始化为传入的参数(`initialValue`)
let?refContainer?=?useRef(initialValue)?

<JSX?ref={refContainer}?...
refContainer.current.dom操作

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

//函数式组件
function?TextInputWithFocusButton()?{
????//定义了一个ref变量:inputEl
??const?inputEl?=?useRef(null);
    //console.log(inputEl.current)--输出就是null
??
??const?onButtonClick?=?()?=>?{
????//?`current`?指向已挂载到?DOM?上的文本输入元素
      //点击的时候获取焦点--改变value
????inputEl.current.focus();
     inputEl.current.value="皮皮虾"
       
??};
????
??return?(
????<div className="App">
      //在这里传给ref
??????<input?ref={inputEl}?type="text"?/>
??????<button?onClick={onButtonClick}>Focus?the?input</button>
????</>
??);
}

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是不是简单的多得多得多了呢?
        </div>
    );
}

useReducer

入redux的状态管理,是一个min版的redux,不支持中间件redux-thunk。

示例:

//reducers.js

export let initState ={
    count:0
}

export let reducer = (state=initState,action)=>{
    let {type,payload} = action;

    switch(type){
        case "ADD":return {
            count:state.count+1
        }
        default:return initState;
    }    
}

//组件里

import {useReducer} from "react";
import {initState,reducer} from "./reducer";

export default ()=>{
    let [state,dispatch] = useReducer(reducer,initState);
    let handleClick = ()=>{
        dispatch({
            type:"ADD"
        });
    }
    return (
        <div>
            <h1>userReducer的使用</h1>
            <p>{state.count}</p>
            <input type="button" value=" 加一 " onClick={handleClick} />
        </div>
    );
}

useCallback 记忆函数

以前写类组件时,经常会写以下(有隐含性能的问题)代码

export default class Parent extends React.Component {   
    
    tempfn(v){
        
    }    
   
    render = () => (
        <div>
             <Son person={this.obj} onMyClick={(val)=>this.tempFn(val)} />
        </div>
    )
}

? 以上代码存在的问题,当父组件(Parent)重新渲染引起子组件Son的渲染。那么就会重新定义对象:{name:张三疯} ,也还会重新定义函数(val)=>this.tempFn(val)。diff算法在比较虚拟dom时,发现有变化,就会渲染的更多。耗费cpu性能和内存。如果说这两个对象在渲染前后,没有变化,那么,就会节约性能。

? 解决办法:把它们抽取成变量。如下:

export default class Parent extends React.Component {   
    constructor(props){
        super(props);
        this.state={
            person:{name:"张三疯"}
        }
        this.tempFn = this.tempFn.bind(this);
    }
    tempFn(val){
        
    }
    render = () => (
        <div>
             <Son person={this.state.person} onMyClick={this.tempFn} />
        </div>
    )
}

? 但是,在函数式组件里,就没有办法了,因为,类组件里可以用this存储这个函数,而在函数式组件了,没法存储这个函数。又因为,函数式组件,每次重新渲染时,会把函数里的代码全部执行,而类组件只执行render函数里的代码(你可以认为函数式组件就是类组件中的render方法) ??难道对象和函数不能定义在函数式组件的外面吗??,答案是不行(你可以试试)。

useCallBack能干什么?

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

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

格式:

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

示例代码:

import {useState,useCallback} from "react"

export default ()=>{
    
    //1、 定义一个状态(基本类型):count
    const [count,setCount] = useState(0);
    
    
    let changecount = () => {
        setCount(count + 1)
      }//依赖了count,所以,只要count有变化,就会执行 setCount(count + 1)
 
    

    let changecount = useCallback(() => {
        setCount(count + 1)
      }, [count]) //依赖了count,所以,只要count有变化,就会执行 setCount(count + 1)
 
    let changecount = useCallback(() => {
        setCount(count + 1)
    }, [])//这种写法,没有依赖值,那么不会执行 setCount(count + 1)

    return (
        <div>
            <input type="button" value="修改count2" onClick={changecount} />
            <p>count:{count}</p>
        </div>
    )
}

那么,useCallBack到底在何处提高了性能?

答: 通常在将一个组件中的函数,传递给子元素进行回调使用时,使用useCallback对函数进行处理

示例代码:

1、首先,父组件更新时,子组件也会更新

const MySon = (props)=>{
    console.log("props.name",props.name)
    return  <input type="button" value={props.name} />
}

export default function Myparent() {
  
    const [p,setP] = useState(0)
    
    console.log("父组件更新了");
    return (
      <div>
        <MySon name="btn1" />
        <input type="button" value="加一" onClick={e=>setP(p+1)} />
      </div>
    )
  }

2、如果父组件更新时,不希望子组件更新,使用memo( React.memo()是一个高阶函数,它与 React.PureComponent类似, React.memo()用在函数式组件里,防止无效的更新)

import {useState,memo} from "react"

const MySon = memo((props)=>{
    console.log("props.name",props.name)
    return  <input type="button" value={props.name} />
})

3、当然,如果出现了,父组件修改的数据影响了子组件的数据(props),那么子组件肯定是要更新的

const MySon = memo((props)=>{
    console.log("props.name",props.name)
    return <input type="button" value={props.name} />
})

export default function Myparent() {
  
    const [p,setP] = useState(0);
    const [name,setName]=useState("hi");
    
    console.log("父组件更新了");
    return (
      <div>
        <MySon name={name}  />
         {/*点击这个按钮,子组件不更新*,因为,p的值并没有在子组件中使用/}
        <input type="button" value="加一" onClick={e=>setP(p+1)} /> 
         {/*点击这个按钮,子组件会更新,因为,name是传入子组件的值*/}
        <input type="button" value="修改Name" onClick={e=>setName("haha")} />
      </div>
    )
  }  

4、但是:子组件使用父组件的函数(父组件给子组件传递函数)时,虽然,函数体没有变化,但是父组件更新依然会更新子组件。这不是我们希望的。

解释:在父组件里定义的函数,每次的值(函数体)是不变了的。由于,父组件更新时,重新定义函数(new Function()),那么函数的值(地址)会发生变化。所以,引起子组件的更新了。

const MySon = memo((props)=>{
    console.log("props.name",props.name)
    return <input type="button" value={props.name} onClick={props.increament} />
})

export default function Myparent() {
  
    const [p,setP] = useState(0);
    const [name,setName]=useState("hi");

    const  increament1 = () => {
        console.log('执行increament1')
    }
        
    console.log("父组件更新了");
    return (
      <div>
        <MySon name={name} increament={increament1} />
        {/*点击下面按钮时,看上去并没有修改子组件所使用的的数据name和increment。
          但是,实际上,increment1的值发生变化了。
          因为:
             1、父组件更新时,重新定义了函数(即:重新new Function()),即:变量increment1的值(存储的地址)发生变化了
             2、对于memo来说,只做了了浅比较,只要变量的值(对于对象来说,在内存中存储的就是地址)发生了变化,那么,就会引起组件的更新。
        */}
        <input type="button" value="加一" onClick={e=>setP(p+1)} />
      </div>    
    )
  }

5、useCallBack出场了

首先,假定 increament1函数里依赖着某个数据(如:count),即:当count发生变化时,increament1函数才会重新定义。

其次,把increment1函数用useCallBack进行包裹。这样就不会出现无效的刷新(只要count的值不变,函数increament1就不会重新定义,子组件就不会重新刷新)

 const  increament1 = useCallback(() => {
        console.log('执行increament1')
    },[count])

最终的示例代码:

import {useState,useCallback,memo} from "react"

const MySon = memo((props)=>{
    console.log("props.name",props.name)
    return <input type="button" value={props.name} onClick={props.increament} />
})

export default function Myparent() {
  
    const [p,setP] = useState(0);
    const [name,setName]=useState("hi");
    const [count,setCount] = useState(0);

    //increment1函数是否要重新定义,取决于count的值是否发生变化
    const  increament1 = useCallback(() => {
        console.log('执行increament1')
    },[count])
        
    console.log("父组件更新了");
    return (
      <div>
        <MySon name={name} increament={increament1} />
        {/*点击下面这个按钮,子组件不会刷新,因为,并没有修改count的值*/}
        <input type="button" value="p加一" onClick={e=>setP(p+1)} />
        {/*点击下面这个按钮,子组件会刷新,因为,increment1函数是否要重新定义,取决于count的值是否发生变化,下面这个按钮修改了count的值*/}
        <input type="button" value="count加1" onClick={e=>setCount(count+1)} />
      </div>    
    )
  }

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;

useCallback:

? let 新的函数 = useCallback(原函数,[依赖]);

useMemo:

? let 原函数的返回值 = useMemo(()=>原函数(),[依赖]);

useLayoutEffect 同步执行副作用

路由相关的hooks

? 在非路由跳转的组件里,要获取路由上下文对象,除了可以使用高阶组件withRouter外,react-router-dom里还提供了hooks。

useHistory():

? useHistory 返回一个 路由上下文上的history 对象

import { useHistory } from "react-router-dom";

export default (props)=>{
    let history = useHistory();
    console.log("我是用户添加页面:props",props);
    return (
        <div>
            <h1>我是用户添加页面</h1>
            <input type="button" value="跳转" onClick={()=>history.push("/")} />
        </div>
    );
}

useLocation()

? useLocation() 返回一个路由上下文的 location 对象。

<Link to={{pathname:"/User",query:{"id":"01002"}}}>用户管理</Link>

import { useHistory,useLocation } from "react-router-dom";

export default ()=>{
    let location = useLocation();
    let history = useHistory();
    console.log("location",location);
    return (
        <div>
            <h1>我是用户添加页面</h1>
            <input type="button" value="跳转" onClick={()=>history.push("/")} />
        </div>
    );
}

useParams()

? useParams() 返回当前匹配的路径上的 params

 ````JS
用户管理

import { useHistory,useLocation,useParams } from “react-router-dom”;

export default ()=>{
let location = useLocation();
let history = useHistory();
let params = useParams();

console.log("location",location);
console.log("params",params);

return (
    <div>
        <h1>我是用户添加页面</h1>
        <input type="button" value="跳转" onClick={()=>history.push("/")} />
    </div>
);

}
````

useRouteMatch

? useRouteMatch 可以有一个参数 path,如果什么都不传,会返回当前 context 上的 match 的值,一定是 true。如果传了 path,会比较这个 path 和当前 location 是否 match。

   </div>
);

}




useParams()

?     useParams() 返回当前匹配的路径上的 params 

     ````JS


 <Link to={{pathname:"/User/01003"}}>用户管理</Link>

import { useHistory,useLocation,useParams } from "react-router-dom";

export default ()=>{
    let location = useLocation();
    let history = useHistory();
    let params = useParams();

    console.log("location",location);
    console.log("params",params);
    
    return (
        <div>
            <h1>我是用户添加页面</h1>
            <input type="button" value="跳转" onClick={()=>history.push("/")} />
        </div>
    );


}
     ````



useRouteMatch

?        useRouteMatch 可以有一个参数 path,如果什么都不传,会返回当前 context 上的 match 的值,一定是 true。如果传了 path,会比较这个 path 和当前 location 是否 match。 

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-07 11:54:23  更:2021-08-07 11:59:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 6:42:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计