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?看做?componentDidMount ,componentDidUpdate —渲染完毕做的事 和?componentWillUnmount ?这三个函数的组合。 ??????????我们在函数式组件里,没有?componentDidMount ,componentDidUpdate ?和?componentWillUnmount ,用useEffect。即:当数据发生变化后,渲染到组件上,组件渲染完毕后,就会调用useEffect。 格式:
useEffect(回调函数,[依赖]);
1、useEffect的无条件执行(只有一个参数)
import React,{useState,useEffect} from 'react';
function App() {
const [count,setCount] = useState(0);
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。
示例:
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 ()=>{
const [count,setCount] = useState(0);
let changecount = () => {
setCount(count + 1)
}
let changecount = useCallback(() => {
setCount(count + 1)
}, [count])
let changecount = useCallback(() => {
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} />
{}
<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} />
{}
<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);
const increament1 = useCallback(() => {
console.log('执行increament1')
},[count])
console.log("父组件更新了");
return (
<div>
<MySon name={name} increament={increament1} />
{}
<input type="button" value="p加一" onClick={e=>setP(p+1)} />
{}
<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);
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。
|