React hook超详细教程
目录
- 什么是hook
- useState
- useEffect
- useRef
- useCallback
- useMemo
- useContext
- useReducer
什么是hook
React Hook是React 16.8版本之后添加的新属性,用最简单的话来说,React Hook就是一些React提供的内置函数,这些函数可以让函数组件和类组件一样能够拥有组件状态(state)以及进行副作用(side effect)
但是不要什么业务都使用hook,请在合适的时候使用hook,否则会造成性能问题.(能不用的时候就不能,当遇到性能不好优化的时候,自然会想到使用它)
useState
它允许函数组件将自己的状态持久化到React运行时的某个地方,这样在组件每次重新渲染的时候都可以从这个地方拿到该状态,而且当该状态被更新的时候,组件也会重渲染。
//语法: import {useState} from "react" const [state, setState] = useState(initialState)//数组解构赋值 //useState接收一个initialState变量作为状态的初始值,返回值是一个数组。返回数组的第一个元素代表当前state的最新值,第二个元素是一个用来更新state的函数。state和setState这两个变量的命名是你自己取的 //state用于组件内部使用的数据 //setState函数用于修改state,当修改后会触发所有使用过state的地方重新取值(调用render) //可以用多个useState
案例:
import React, { Component,useState } from 'react' export default function Box3() { const [first, setfirst] = useState(0) return ( <div> <h1>{first}</h1> <button onClick={()=>{setfirst(first+1)}}>点击first+1</button> </div> ) }
可以看到数据修改了并刷新了模板
useEffect
useEffect是用来使函数组件也可以进行副作用操作的。那么什么是副作用呢?
函数的副作用就是函数除了返回值外对外界环境造成的其它影响假如我们每次执行一个函数,该函数都会操作全局的一个变量,那么对全局变量的操作就是这个函数的副作用。而在React的世界里,我们的副作用大体可以分为两类,一类是调用浏览器的API,例如使用addEventListener来添加事件监听函数等,另外一类是发起获取服务器数据的请求,例如当用户组件挂载的时候去异步获取用户的信息等。
import {useEffect} from "react" useEffect(effect?=>clean, dependencies?) //useEffect的第一个参数effect是要执行的副作用函数,它可以是任意的用户自定义函数,用户可以在这个函数里面 操作一些浏览器的API或者和外部环境进行交互,网络请求等,这个函数会在每次组件渲染完成之后被调用 //useEffect可以有一个返回值,返回一个函数,系统在组件重新渲染之前调用它 //第二个参数dependencies来限制该副作用的执行条件
案例:组件销毁时清除计算器
import React,{useEffect,useState} from 'react' export default function Box1(props) { let [i,seti]=useState(0) useEffect(()=>{ console.log(i) let timer=setInterval(() => { seti(i+1) },1000); return ()=>{ clearInterval(timer) } }) return ( <div> <h1>{i}</h1> </div> ) }
//父组件 import React,{useState} from 'react' import Box1 from './Box1' export default function Box2() { let [flag,setflag]=useState(true) return ( <div> <button onClick={()=>{setflag(!flag)}}>点击销毁/创建Box1</button> {flag&&<Box1></Box1>} </div> ) }
useRef
useRef是用来在组件不同渲染之间共用一些数据的,它的作用和我们在类组件里面为this赋值是一样的。
语法
react import {useRef} from "react" const refObject = useRef(initialValue) //useRef接收initialValue作为初始值,它的返回值是一个ref对象,这个对象的.current属性就是该数据的最新值。使用useRef的一个最简单的情况就是在函数组件里面获取DOM对象的引用
案例:
import { useRef, useEffect } from 'react' import ReactDOM from 'react-dom' const AutoFocusInput = () => { const inputRef = useRef(null) useEffect(() => { // 组件挂载后自动聚焦 inputRef.current.focus() }, []) return ( <input ref={inputRef} type='text' /> ) } ReactDOM.render(<AutoFocusInput />, document.getElementById('root')) //在上面代码中inputRef其实就是一个{current: input节点}对象,只不过它可以保证在组件每次渲染的时候拿到的都是同一个对象。
useCallback
useCallback就是把我们在函数组件内部定义的函数保存起来,当组件重新渲染时还是使用之前的,就不会被重新定义一次
语法:
import {useCallback} from "react" const memoizedCallback = useCallback(callback, dependencies) //useCallback接收两个参数,第一个参数是需要被记住的函数,第二个参数是这个函数的dependencies,只有dependencies数组里面的元素的值发生变化时useCallback才会返回新定义的函数,否则useCallback都会返回之前定义的函数。
案例:
import React,{useCallback,useEffect,useState} from 'react' export default function Box7() { let [arr,setarr]=useState([{id:1,name:'ljy'},{id:2,name:'jack'},{id:3,name:'marry'}]) let fn=useCallback((index)=>{ console.log(index) },[]) useEffect(()=>{ console.log('fn变化了') },[fn]) return ( <div> <>{arr.map(el=><p key={el.id}><span>{el.id}---{el.name}</span></p>)}</> <button onClick={()=>{setarr([...arr])}}>点击修改数据</button> </div> ) }
useMemo
useMemo和useCallback的作用十分类似,只不过它允许你记住任何类型的变量(不只是函数)
import {useMemo} from "react" const memoizedValue = useMemo(() => valueNeededToBeMemoized, dependencies) //useMemo接收一个函数,该函数的返回值就是需要被记住的变量,当useMemo的第二个参数dependencies数组里面的元素的值没有发生变化的时候,memoizedValue使用的就是上一次的值。
案例:
import React, { useMemo } from 'react' import ReactDOM from 'react-dom' const RenderPrimes = ({ iterations, multiplier }) => { const primes = React.useMemo(() => calculatePrimes(iterations, multiplier), [ iterations, multiplier ]) return ( <div> Primes! {primes} </div> ) } ReactDOM.render(<RenderPrimes />, document.getElementById('root')) //例子中calculatePrimes是用来计算素数的,因此每次调用它都需要消耗大量的计算资源。 为了提高组件渲染的性能,我们可以使用useMemo来记住计算的结果, 当iterations和multiplier保持不变的时候,我们就不需要重新执行calculatePrimes函数来重新计算了,直接使用上一次的结果即可。
useContext
我们知道React中组件之间传递参数的方式是props,假如我们在父级组件中定义了某些状态,而这些状态需要在该组件深层次嵌套的子组件中被使用的话就需要将这些状态以props的形式层层传递,这就造成了props drilling的问题。为了解决这个问题,React允许我们使用Context来在父级组件和底下任意层次的子组件之间传递状态。在函数组件中我们可以使用useContext Hook来使用Context。
语法:
const value = useContext(MyContext) //useContext接收一个context对象为参数,该context对象是由React.createContext函数生成的。 useContext的返回值是当前context的值,这个值是由最邻近的<MyContext.Provider>来决定的。 一旦在某个组件里面使用了useContext这就相当于该组件订阅了这个context的变化, 当最近的<MyContext.Provider>的context值发生变化时,使用到该context的子组件就会被触发重渲染,且它们会拿到context的最新值。
案例:
import React, { useContext, useState } from 'react' import ReactDOM from 'react-dom' //定义context const NumberContext = React.createContext() const NumberDisplay = () => { const [currentNumber, setCurrentNumber] = useContext(NumberContext) const handleCurrentNumberChange = () => { setCurrentNumber(Math.floor(Math.random() * 100)) } return ( <> <div>Current number is: {currentNumber}</div> <button onClick={handleCurrentNumberChange}>Change current number</button> </> ) } const ParentComponent = () => { const [currentNumber, setCurrentNumber] = useState(100) return ( <NumberContext.Provider value={[currentNumber, setCurrentNumber]}> <NumberDisplay /> //这里填儿子组件,后面孙子组件就可以直接使用爷爷组件传递的值 </NumberContext.Provider> ) } ReactDOM.render(<ParentComponent />, document.getElementById('root'))
使用时避免无用渲染
如果一个函数组件使用了useContext(SomeContext)的话它就订阅了这个SomeContext的变化,这样当SomeContext.Provider的value发生变化的时候,这个组件就会被重新渲染。
这里有一个问题就是,我们可能会把很多不同的数据放在同一个context里面,而不同的子组件可能只关心这个context的某一部分数据,当context里面的任意值发生变化的时候,无论这些组件用不用到这些数据它们都会被重新渲染,这可能会造成一些性能问题.
解决方法:
1.拆分Context
这个方法是最被推荐的做法,和useState一样,我们可以将不需要同时改变的context拆分成不同的context,让它们的职责更加分明,这样子组件只会订阅那些它们需要订阅的context从而避免无用的重渲染。
import React, { useContext, useState } from 'react' import ExpensiveTree from 'somewhere/ExpensiveTree' import ReactDOM from 'react-dom' const ThemeContext = React.createContext() const ConfigurationContext = React.createContext() const ChildrenComponent = () => { const [themeContext] = useContext(ThemeContext) return ( <div> <ExpensiveTree theme={themeContext} /> </div> ) } const App = () => { const [themeContext, setThemeContext] = useState({ color: 'red' }) const [configurationContext, setConfigurationContext] = useState({ showTips: false }) return ( <ThemeContext.Provider value={[themeContext, setThemeContext]}> <ConfigurationContext.Provider value={[configurationContext, setConfigurationContext]}> <ChildrenComponent /> </ConfigurationContext.Provider> </ThemeContext.Provider> ) } ReactDOM.render(<App />, document.getElementById('root'))
2.拆分组件,使用memo来优化消耗性能的组件
如果出于某些原因你不能拆分context,仍然可以通过将消耗性能的组件和父组件的其他部分分离开来,并且使用memo函数来优化消耗性能的组件
import React, { useContext, useState } from 'react' import ExpensiveTree from 'somewhere/ExpensiveTree' import ReactDOM from 'react-dom' const AppContext = React.createContext() const ExpensiveComponentWrapper = React.memo(({ theme }) => { return ( <ExpensiveTree theme={theme} /> ) }) const ChildrenComponent = () => { const [appContext] = useContext(AppContext) const theme = appContext.theme return { <div> <ExpensiveComponentWrapper theme={theme} /> </div> ) } const App = () => { const [appContext, setAppContext] = useState({ theme: { color: 'red' }, configuration: { showTips: false }}) return ( <AppContext.Provider value={[appContext, setAppContext]}> <ChildrenComponent /> </AppContext.Provider> ) } ReactDOM.render(<App />, document.getElementById('root'))
3.不拆分组件,也可以使用useMemo来优化
import React, { useContext, useState, useMemo } from 'react' import ExpensiveTree from 'somewhere/ExpensiveTree' import ReactDOM from 'react-dom' const AppContext = React.createContext() const ChildrenComponent = () => { const [appContext] = useContext(AppContext) const theme = appContext.theme return useMemo(() => ( <div> <ExpensiveTree theme={theme} /> </div> ), [theme] ) } const App = () => { const [appContext, setAppContext] = useState({ theme: { color: 'red' }, configuration: { showTips: false }}) return ( <AppContext.Provider value={[appContext, setAppContext]}> <ChildrenComponent /> </AppContext.Provider> ) } ReactDOM.render(<App />, document.getElementById('root'))
useReducer
.useReducer用最简单的话来说就是允许我们在函数组件里面像使用redux一样通过reducer和action来管理我们组件状态的变换
语法:
const [state, dispatch] = useReducer(reducer, initialArg, init?) //useReducer和useState类似,都是用来管理组件状态的,只不过和useState的setState不一样的是,useReducer返回的dispatch函数是用来触发某些改变state的action而不是直接设置state的值,至于不同的action如何产生新的state的值则在reducer里面定义。 //useReducer接收的三个参数分别是: //reducer: 这是一个函数,它的签名是(currentState, action) => newState,从它的函数签名可以看出它会接收当前的state和当前dispatch的action为参数,然后返回下一个state,也就是说它负责状态转换的工作。 //initialArg:如果调用者没有提供第三个init参数,这个参数代表的是这个reducer的初始状态,如果init参数有被指定的话,initialArg会被作为参数传进init函数来生成初始状态。 //init: 这是一个用来生成初始状态的函数,它的函数签名是(initialArg) => initialState,从它的函数签名可以看出它会接收useReducer的第二个参数initialArg作为参数,并生成一个初始状态initialState
案例:
import React,{useReducer} from 'react' export default function Box3() { let redux=(initState,action)=>{ if(action.type=='NAME'){ //这里进行判断type是什么,然后修改对应的值 initState.name=action.value } initState=JSON.parse(JSON.stringify(initState)) //这一步是状态转换,返回一个新的initState,没有这一步则修改之后不会刷新 return initState } let [state,dispatch]=useReducer(redux,{name:'hello'},(arg)=>{ arg.name="ljy" return arg //return返回的就是初始值 }) return ( <> <h1>{state.name}</h1> <button onClick={()=>{dispatch({type:'NAME',value:'修改了name'})}}>点击修改</button> </> ) }
useReducer +useContext实现redux
第一步:外部创建上下文对象
/ctx.ctx.js import React from 'react' let ctx=React.createContext() export default ctx
第二步:定义生产者,并在其调用useReducer,将值通过value传给后代
import React,{useReducer} from 'react' import ctx from './ctx/ctx' export default function App(props) { let redux=(initState,action)=>{ if(action.type=='NAME'){ initState.name=action.value } initState=JSON.parse(JSON.stringify(initState)) return initState } let [state,dispatch]=useReducer(redux,{name:'hello'},(arg)=>{ arg.name="ljy" return arg }) return ( <ctx.Provider value={[state,dispatch]}> {/* 传给后代组件state,和修改的方法dispatch */} {props.children} {/* 相当于Vue中的插槽的用法 */} </ctx.Provider> ) } 第三步:index.js文件中挂载
import React from ‘react'; import ReactDOM from ‘react-dom/client'; import App from ‘./App' import Box1 from ‘./Box1' const root = ReactDOM.createRoot(document.getElementById(‘root')); root.render();
第四步:子组件或孙组件使用
import React,{useContext} from 'react' import ctx from './ctx/ctx' import Box2 from './Box2' export default function Box1() { let [state,dispatch]=useContext(ctx) console.log(state) return ( <> <h1>Box1中---{state.name}</h1> <button onClick={()=>{dispatch({type:"NAME",value:'ljy666'})}}>修改state</button> <Box2></Box2> </> ) } import { type } from '@testing-library/user-event/dist/type' import React,{useContext} from 'react' import ctx from './ctx/ctx' export default function Box2() { let [state,dispatch]=useContext(ctx) return ( <> <h1>Box2中---{state.name}</h1> <button onClick={()=>{dispatch({type:'NAME',value:'孙组件修改了'})}}>点击修改</button> </> ) }
到此这篇关于React hook超详细教程的文章就介绍到这了,更多相关React hook内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!