react在16.8版本后添加hooks(钩子函数),hooks是为了配合静态组件实现类组件的功能,它有着天然的优势:
(1)对静态组件没有破坏性改动,只是在原有代码上增加功能
(2)没有this
(3)函数式编程
(4)通过自定义hooks实现公共代码抽离
接下来就让我们来了解各种hooks函数和用法吧~
1.useState() 让静态组件有自身状态state
useState使静态组件拥有state实现自身状态更新,无需一直this访问内部state了
例:实现计数器
import { useState } from 'react';
const App=()=>{
const [count,setCount]=useState(0);
//[初始state,更新state的方法] = useState(state初始值)
return(
<>
<p>count:{count}</p>
<button onClick={()=>{setCount(count+1)}}>点击加一</button>
</>
)
}
export default App;
效果如下:
2.useEffect()让静态组件拥有自己的生命周期方法
useEffect有四种情况 取决于他的参数个数和是否带参
<1>useEffect(()=>{}); 相当于类组件的componentDidMount+componentDidUpdate
说明:组件初始化执行一次,任何state改变都会执行一次
<2>useEffect(()=>{},[state]); 相当于类组件的componentDidMount+componentDidUpdate
说明:组件初始化执行一次,仅指定state改变才执行一次
<3>useEffect(()=>{},[]); 仅相当于类组件的componentDidMount
说明:组件初始化执行一次,更新state不执行
说明2:初始化组件的ajax、绘制图形库ECharts
<4>useEffect(()=>{
//代码
return ()=>{//相当于类组件的componentWillUNmount
}
},[])
四种情况就不一一演示了
就演示一个 指定state改变执行触发useEffect的案例吧
import { useState,useEffect } from 'react';
import axios from 'axios';
const App=()=>{
const [email,setEmail]=useState('');
const [loading,setLoading]=useState(true);
//思路:loding为true时获取数据,获取到数据后更新email,将loading刷成false
useEffect(()=>{
const asyncEmail = async()=>{
if(loading){
const res = await axios.get("https:/randomuser.me/api")
setEmail(res.data.results[0].email);
setLoading(false);
}
}
asyncEmail();
},[loading])
return(
<>
<p>email:{loading?"...loading":email}</p>
<button onClick={()=>{setLoading(!loading)}}>点击获取随机email</button>
</>
)
}
export default App;
3.useReducer:修改state另一种方式,是useState替代方法
import { useReducer } from "react";
//创建初始state
let initialState = {count:0};
//创建reducer函数
const counter = (state,action)=>{
switch(action.type){
case 'ADD':
return {count:state.count+1};
case 'SUB':
return {count:state.count-1};
default:
throw new Error('发生错误')
}
}
const App = () => {
let [state,dispatch] = useReducer(counter,initialState)
//调用useReducer功能
//1.注册counter这个reducer函数
//2.返回一个数组[initialState,f(){}]
// console.log(state,dispatch);
return (
<>
<h3>App组件</h3>
<p>count:{state.count}</p>
<button onClick={()=>{dispatch({type:"ADD"})}}>+</button>
<button onClick={()=>{dispatch({type:"SUB"})}}>-</button>
</>
);
}
export default App;
多个状态互不影响
import { useReducer } from "react";
let initialState = {
count1:0,
count2:0
};
//创建reducer函数
const counter = (state,action)=>{
switch(action.type){
case 'ADD1':
return {...state,count1:state.count1+1};
case 'SUB1':
return {...state,count1:state.count1-1};
case 'ADD2':
return {...state,count2:state.count2+1};
case 'SUB2':
return {...state,count2:state.count2-1};
default:
throw new Error('发生错误');
}
}
const App = () => {
let [state,dispatch] = useReducer(counter,initialState);
return (
<>
<h3>App组件</h3>
<p>count1:{state.count1}</p>
<button onClick={()=>{dispatch({type:"ADD1"})}}>+</button>
<button onClick={()=>{dispatch({type:"SUB1"})}}>-</button>
<hr/>
<p>count2:{state.count2}</p>
<button onClick={()=>{dispatch({type:"ADD2"})}}>+</button>
<button onClick={()=>{dispatch({type:"SUB2"})}}>-</button>
</>
);
}
export default App;
4.useContext 实现全局数据共享—组件跨层通信
创建countContext.js文件
import React from "react";
export const CounterContext = React.createContext();
创建父组件
import React,{useState} from "react";
import {CounterContext} from './CounterContext'
import Child from './Child';
const App = () => {
const [count,setCount] = useState(0);
return (
<>
<h3>App组件</h3>
<p>count:{count}</p>
<button onClick={()=>setCount(count+1)}>+</button>
<CounterContext.Provider value={count} >
<Child />
</CounterContext.Provider>
</>
);
}
export default App;
在子组件/后代组件中使用
import { useContext } from "react";
import {CounterContext} from './CounterContext'
const Child = () => {
let count = useContext(CounterContext);
return (
<>
<h3>Child组件</h3>
<p>count:{count}</p>
</>
);
}
export default Child;
5.useRef返回一个ref对象{current:null},current属性存储DOM对象**--功能类似于类组件的this.refs、React.createRef()
特点: <1>ref对象的 current属性可以存储DOM对象也可以存储其他类型数据
<2> 组件每次渲染的时候,返回同一个ref对象
<3>对象在组件的整个生命周期内持续存在
<4>变更 .current 属性不会引发组件重新渲染
案例:点击按钮,input框获得焦点
import {useRef} from 'react';
const App = () => {
let inputRef = useRef(null);//创建了一个ref对象,初始值为null {current:null}
console.log('inputRef',inputRef);//null
let handdleClick=()=>{
console.log('inputRef',inputRef);//ref属性赋值的DOM对象
inputRef.current.focus();//让文本框获得焦点
console.log("获得的文本框的内容",inputRef.current.value);
}
return (
<div>
<h3>App组件</h3>
<input type="text" ref={inputRef} />
{/* ref属性的功能:将DOM对象赋值给current属性 {current:input DOM对象} */}
<button onClick={handdleClick}>点击让文本框获得焦点</button>
</div>
);
}
export default App;
拓展:ref属性--ref对象和ref属性是不同的
- ref属性的值可以是ref对象,也可以是回调函数*
问题:变更.current属性不会引发组件从新渲染
解决:给ref属性赋值为回调函数,根据DOM变化,改变state引发从新渲染
案例:
import React,{useState,useCallback,useRef} from 'react';
const App = () => {
let [height,setHeight] = useState(0);
let inputRef = useRef()
let myRef = useCallback(node=>{
console.log('node',node);
inputRef.current=node;//将当前ref属性所在DOM赋值给ref对象的current属性
setHeight(node.getBoundingClientRect().height);
inputRef.current.innerHTML="我是小甜儿";//修改h1的内容
},[])
return (
<div>
<h3>App组件</h3>
<h1 ref={myRef}>哈哈哈哈</h1>
<p>h1的高度是:{Math.ceil(height)}px</p>
</div>
);
}
export default App;
/*
getBoundingClientReact() :
Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置
width-- 元素的实际宽度+padding+border-width
height-- 元素的实际高度+padding+border-height
相对于视口:
top -- 元素顶部到视口顶部的距离
left -- 元素左侧到视口左侧的距离
bottom -- 元素底部到视口顶部的距离 (相当于height+top)
right -- 元素右侧到视口左侧的距离 (相当于width+left)
除了 width 和 height 以外的属性是相对于视图窗口的左上角来计算的。
*/
6. forwardRef():将ref转发给子组件
说明:将ref属性的值转发给子组件,可以在父组件中操作子组件的DOM对象
App组件:
import React,{useRef} from 'react';
import Child from './Child';
const App = () => {
const inputRef = useRef(null);
const getFocus = ()=>{
inputRef.current.focus();
// console.log(inputRef.current.value)
}
return (
<div>
<h3>App组件</h3>
<button onClick={getFocus}>点击让子组件的文本框获得焦点</button>
<hr />
<Child ref={inputRef}/>
</div>
);
}
export default App;
Child组件:
import React,{forwardRef} from "react";
//forwardRef 可以将ref传给子组件(让子组件接收父组件穿过来的ref)
// 就相当于 用子组件的ref属性给父组件的ref属性赋了个值
// 在这个过程中涉及的接收问题 forwardRef就给了子组件一个变量能接收父组件传过来的ref对象
// forwardRef 也就相当于是高阶组件
const Child = forwardRef((props,parentRef) => {//ref随便起(就是用来接收父组件穿过来的ref对象的)
return (
<div>
<h3>Child组件</h3>
<input type="text" ref={parentRef} />
</div>
);
})
export default Child;
存在问题: 使用forward+useRef获取子函数式组件DOM时,获取到的dom属性暴露的太多了
解决: 使用uesImperativeHandle解决,在子函数式组件中定义父组件需要进行DOM操作,减少获取DOM暴露的属性过多
7.useImperativeHandle可以只暴露DOM对象特定的操作
useImperativeHandle(ref,createHandle)
说明:将父组件传入的ref和useImperativeHandle的第二个参数返回的对象绑定到一起,这样在父组件中调用inputRef,current时,实际上是返回的对象
App组件:
import React,{useRef,useState} from 'react';
import Child from './Child';
const App = () => {
const inputRef = useRef(null);
// useImperativeHandle 主要作用:用于减少父组件中通过forward+useRef获取子组件DOM元素暴露的属性过多
let getFocus = ()=>{
inputRef.current.focus();
//这里的focus不再是子组件DOM对象的focus
//而是 子组件创建的focus事件(这个事件里子组件通过自己的ref对象,操作自己的DOM获得焦点)
console.log(inputRef.current.value());
}
return (
<div>
<h3>App组件</h3>
<button onClick={getFocus}>点击让子组件的文本框获得焦点</button>
<hr />
<Child ref={inputRef} />
</div>
);
}
export default App;
Child组件:
import React,{useRef,forwardRef,useImperativeHandle} from "react";
const Child = forwardRef((props,parentRef) => {//ref随便起(就是用来接收父组件穿过来的ref对象的)
const inputChildRef = useRef();
useImperativeHandle(parentRef,()=>({//这个函数赋值给了父组件的ref属性(parentRef)
focus:()=>{//父组件调用的focus事件
inputChildRef.current.focus();//通过子组件自己的ref来操作DOM
},
value:(y)=>{
let childValue = inputChildRef.current.value;
return childValue;
},
}))
return (
<div>
<h3>Child组件</h3>
<input type="text" ref={inputChildRef} />
</div>
);
})
export default Child;
8.useLayoutEffect:类似于useEffect
说明:useLayoutEffect 功能和类组件的componentDidMount 完全等价
useLayoutEffect和useEffect不同点:
-
useEffect 是异步执行的,而useLayoutEffect是同步执行的。
-
useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前,和 componentDidMount 等价。
案例:
import React, { useEffect, useLayoutEffect, useState } from 'react';
import './App.css';
function App() {
const [state, setState] = useState("hello world")
// useEffect(() => { //先看到 hello world while后刷成world hello的
// let i = 0;
// while(i <= 1000000000) {
// i++;
// };
// setState("world hello");
// }, [])
useLayoutEffect(() => { //直接看到的就是word hello 因为是在浏览器把内容真正渲染到界面之前执行的
let i = 0;
while (i <= 1000000000) {
i++;
};
setState("world hello");
}, []);
/*
因为 useEffect 是渲染完之后异步执行的,所以会导致 hello world 先被渲染到了屏幕上,
再变成 world hello,就会出现闪烁现象。而 useLayoutEffect 是渲染之前同步执行的,
所以会等它执行完再渲染上去,就避免了闪烁现象。
也就是说我们最好把操作 dom 的相关操作放到 useLayouteEffect 中去,避免导致闪烁。
*/
return (
<>
<div>{state}</div>
</>
);
}
export default App;
9.useDebugValue 可用于在 React dev 开发者工具中显示自定义 hook 的标签。了解就行
import React, { useState, useDebugValue } from 'react';
// 自定义 Hook
function useMyCount(num) {
const [count, setCount] = useState(0);
// 延迟格式化
useDebugValue(count > num ? '溢出' : '不足', status => {
return status === '溢出' ? 1 : 0;
});
const myCount = () => {
setCount(count + 2);
}
return [count, myCount];
}
function App() {
const [count, seCount] = useMyCount(10);
return (
<div>
{count}
<button onClick={() => seCount()}>setCount</button>
</div>
)
}
export default App;
大于10的时候开发者工具会显示自定义hook的Mycount值为1