useState
- 使用useState可以让函数式组件也能拥有状态(state)
- 语法:
const [xxx, setXxx] = React.useState(initValue)
initValue是初始值 - useState()说明: 参数: 第一次初始化指定的值在内部作缓存 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
- setXxx()2种写法:
// 1 . setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
const [count,setCount] = React.useState(0)
function add(){
setCount(count+1)
}
// 2 . 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
const [count,setCount] = React.useState(0)
function add(){
setCount(value=>value+1)
}
useEffect
- 使用useEffect,能让函数式组件完成类似生命周期的功能
语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 相当于componentWillUnmount
}
}, [stateValue])
// 如果指定的是[], 回调函数只会在第一次render()后执行
// 如果没有的话挂载和每次更新都会执行
- useEffect第一个参数的return后面是一个函数,该函数相当于
componentWillUnmount
- useEffect第二个参数不填的话,那么每次渲染都会执行,相当于
componentDidUpdate
- useEffect第二个参数是空数组的话,相当于
componentDidMount
- useEffect第二个参数可以指定某个属性变化了之后再执行
const [count, setcount] = React.useState(0)
const [name, setname] = React.useState('zxx')
React.useEffect(()=>{
console.log('count变化了才执行,name变化不执行')
},[count])
useLayoutEffect
- 用法和
useEffect
一样,但是效果有略微的差别 useEffect
会在渲染的内容更新到DOM上后执行,不会阻塞DOM更新useLayoutEffect
会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新useLayoutEffect
的执行会先于useEffect
,所以想要修改组件元素样式建议用useLayoutEffect
useContext
useContext
可以帮助我们跨越组件层级直接传递变量,实现共享- 首先在父组件中
// 想要通过context传递两个数据
import React ,{useContext}from 'react'
const CountContext = React.createContext()
const NameContext = React.createContext()
export default function Father() {
return (
<div>
<CountContext.Provider value={15}>
<NameContext.Provider value={'sss'}>
<Son/>
</NameContext.Provider>
</CountContext.Provider>
</div>
)
}
- 在后代组件中接受
function Son(){
//useContext 的参数必须是 context 对象本身,没有的话就自己导入
const countInfo = useContext(CountContext)
const nameInfo = useContext(NameContext)
return (
<div>
<h2>Header组件</h2>
<p>{countInfo}</p>
<p>{nameInfo}</p>
</div>
)
}
useReducer
useReducer
仅仅是useState的一种替代方案。如果state
处理逻辑比较复杂,我们可以用useReducer
来拆分
const [状态,dispatch] = useReducer(reducer,初始化状态的值)
import React ,{useReducer}from 'react'
// 第一步创建reducer,接受两个参数state和action
function reducer(state,action){
switch (action.type) {
case 'increment':
return {...state,num:state.num+1}
case 'decrement':
return {...state,num:state.num-1}
default:
throw new Error();
}
}
function Home(){
// 使用useReducer
const [state,dispatch] = useReducer(reducer,{num:0})
return (
<div>
<h2>Header数据:{state.num}</h2>
{/*派发action*/}
<button onClick={()=>dispatch({type:'decrement'})}>-1</button>
<button onClick={()=>dispatch({type:'increment'})}>+1</button>
</div>
)
}
function About(){
const [state,dispatch] = useReducer(reducer,{num:25})
return(
<>
<h2>About数据:{state.num}</h2>
<button onClick={()=>dispatch({type:'decrement'})}>-1</button>
<button onClick={()=>dispatch({type:'increment'})}>+1</button>
</>
)
}
export default function App() {
return (
<div>
<Home></Home>
<About></About>
</div>
)
}
useCallback
useCallback
实际的目的是为了进行性能优化
useCallback
会返回一个函数的memoized值- 在依赖不变的情况下,多次定义的时候,返回的值是相同的
搭配
memo
使用:当不使用memo
的时候,父组件重新渲染,子组件必定重新渲染,所以使用memo(子组件),可以避免这个情况发生
import React,{useState,memo,useCallback} from 'react'
// 没使用useCallback的时候,每次增加或者减少,所有的组件都会重新渲染
function Home(props){
console.log("HOME渲染了");
return(
<div>
<p>Home</p>
<button onClick={()=>props.handle()}>减小</button>
</div>
)
}
function About(props){
console.log("ABOUT渲染了");
return(
<div>
<p>About</p>
<button onClick={()=>props.handle()}>增加</button>
</div>
)
}
const MemoHome = memo(Home)
const MemoAbout = memo(About)
export default function App() {
console.log("APP渲染了");
const [numState,setNum] = useState(12)
const [ageState,setAge] = useState(2)
// 只有ageState变化时候,sub这个函数才会变化
const sub = useCallback(
() => {
setAge(ageState-1)
},[ageState],
)
// 只有numState变化时候,add这个函数才会变化
const add = useCallback(
() => {
setNum(numState+2)
},[numState],
)
return (
<div>
<p>num:{numState}</p>
<p>age:{ageState}</p>
<hr />
<MemoHome handle={sub}/>
<MemoAbout handle={add}/>
</div>
)
使用场景:将一个组件中的函数,传递给子组件进行回调使用时,使用useCallback对函数进行处理
useMemo
useCallback
实际的目的也是为了进行性能优化
- 使用场景一:
// 每次点击按钮Home组件都会重新渲染,因为App组件重新渲染导致person的内存地址变化
import React, { memo,useState } from 'react'
const Home = memo((props)=>{
console.log('Home渲染');
return (
<h2>name:{props.person.name}--age:{props.person.age}</h2>
)
})
export default function App() {
const [show, setshow] = useState(false)
let person = {name:'zx',age:15}
return (
<div>
<Home person={person}/>
<button onClick={()=>setshow(!show)}>changeShow</button>
</div>
)
}
- 场景一解决方法:
import React, { memo,useMemo,useState} from 'react'
const Home = memo((props)=>{
console.log('Home渲染');
return (
<h2>name:{props.person.name}--age:{props.person.age}</h2>
)
})
export default function App() {
const [show, setshow] = useState(false)
let person = useMemo(() => {
return {name:'zx',age:15}
}, [])
return (
<div>
<Home person={person}/>
<button onClick={()=>setshow(!show)}>changeShow</button>
</div>
)
}
- 使用场景二
- 如果没有使用useMemo,返回函数的时候,每次App重新渲染都会让getData这个函数重新调用
import React,{useMemo, useState} from 'react'
function getData(){
console.log('getData被调用了');
let data = 0
for(let i=0;i<1000;i++){
data+=i
}
return data
}
export default function App() {
console.log('App组件被渲染了');
const [numState,setNum] = useState(10)
const [ageState] = useState(55)
// const data = getData()
// 通过useMemo可以使得getData不会被重复调用
const data = useMemo(()=>{
return getData()
},[])
return (
<div>
<h2>num:{numState}</h2>
<h2>age:{ageState}</h2>
{/* 修改numState的时候,getData会被重新调用 */}
<button onClick={()=>{setNum(numState+1)}}>修改num</button>
<h2>data:{data}</h2>
</div>
)
}
useRef
useRef可以在函数组件中存储/查找组件内的标签或任意其它数据
- 用法一:引入DOM(或者组件,但是必须是class组件)元素,保存标签对象,功能用法与React.createRef()类似
const 容器的名字= useRef()
import React,{useRef} from 'react'
export default function Demo() {
// 创建ref容器
const myRef = useRef()
function getMsg(){
// 打印通过ref容器获取的元素里面的value值
alert(myRef.current.value)
}
return (
<div>
<input type="text" ref={myRef}/>
<button onClick={getMsg}>获取input信息</button>
</div>
)
}
- 用法二:保存一个数据,这个对象在整个生命周期中可以保持不变
import React,{useRef} from 'react'
export default function Demo() {
// myref去保存数值,除了手动去修改,要不是不会发生变化的
const myRef = useRef(20)
function getMsg(){
// 打印通过ref容器获取的元素里面的value值
console.log(myRef.current) //20
}
return (
<div>
<input type="text" />
<button onClick={getMsg}>获取信息</button>
</div>
)
}
useImperativeHandle
使用useImperativeHandle
之前,首先得了解一下forwardRef
forwardRef
- 函数组件需要使用forwardRef包装才能使用useRef
- 包装之后的函数组件可以接受两个参数,第二个参数就是ref,自己再手动绑定到你需要的元素上
import React, { useRef,forwardRef } from 'react'
function HInput(props,ref){
// 使用了forwardRef之后,他可以接受两个参数,第二个参数是ref
return (
<div>
<input type="text" ref={ref}/>
</div>
)
}
// 函数组件需要使用forwardRef包装才能使用useRef
const NewHInput = forwardRef(HInput)
export default function App() {
const myInput = useRef()
function change(){
myInput.current.focus()
}
return (
<div>
<NewHInput ref={myInput}/>
<button onClick={change}>聚焦子组件input框</button>
</div>
)
}
父组件就能获取到子组件的dom,可以为所欲为.
useImperativeHandle
单纯使用forwardRef,也能满足需求,但是这样父组件就能获取到子组件的dom,所以我们想限制一下父组件的权限,只传递父组件能操作子组件的具体方法.
- 用法:
useImperativeHandle(传递的ref,是一个函数,然后返回一个对象,对象的key就是函数名,value就是函数)
import React, { useRef,forwardRef,useImperativeHandle } from 'react'
function HInput(props,ref){
useImperativeHandle(ref,()=>({
focus:()=>{
console.log('focus');
}
}))
return (
<div>
<input type="text"/>
</div>
)
}
const NewHInput = forwardRef(HInput)
export default function App() {
const myInput = useRef()
function change(){
myInput.current.focus()
}
return (
<div>
<NewHInput ref={myInput}/>
{/* 点击按钮就会执行自己写的foucs方法 */}
<button onClick={change}>聚焦子组件input框</button>
</div>
)
}
自定义Hook
- 自定义hook本质上就是一种函数代码逻辑的抽取。
- 普通函数名前加上use就能使用hook了
- 比如有这么一个需求所有的组件挂载更新和卸载都打印一句话
import React, { useState,useEffect } from 'react'
// 普通函数加上use就能使用hooks了,use后面第一个字母必须大写
function useAddEventListener(component){
useEffect(() => {
console.log(`${component}组件挂载更新`);
return () => {
console.log(`${component}组件卸载`);
}
})
}
function Home(){
useAddEventListener('Home')
return (
<div>Home</div>
)
}
function About(){
useAddEventListener('About')
return (
<div>About</div>
)
}
export default function App() {
const [isShow,setIsShow] = useState(true)
return (
<div>
{isShow && <Home/>}
{isShow && <About/>}
<button onClick={()=>{setIsShow(!isShow)}}>切换</button>
</div>
)
}