useState
useState
是react自带的一个hook函数,它的作用是用来声明状态变量。
从三个方面来看useState
的用法,分别是声明、读取、使用(修改)。
import React, { useState } from 'react';
function Example2(){
const [ age , setAge ] = useState(18)
const [ sex , setSex ] = useState('男')
const [ work , setWork ] = useState('前端程序员')
return (
<div>
<p>今年:{age}岁</p>
<p>性别:{sex}</p>
<p>工作是:{work}</p>
</div>
)
}
export default Example2;
useEffect
绑定和解绑,用于替代生命周期
useEffect两个注意点
- React首次渲染和之后的每次渲染都会调用一遍
useEffect
函数,而之前我们要用两个生命周期函数分别表示首次渲染(componentDidMonut)和更新导致的重新渲染(componentDidUpdate)。 - useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而
componentDidMonut
和componentDidUpdate
中的代码都是同步执行的。
function Example(){
const [ count , setCount ] = useState(0);
useEffect(()=>{
console.log(`useEffect=>You clicked ${count} times`)
return ()=>{
console.log('====================')
}
},[count])
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
<Router>
<ul>
<li> <Link to="/">首页</Link> </li>
<li><Link to="/list/">列表</Link> </li>
</ul>
<Route path="/" exact component={Index} />
<Route path="/list/" component={List} />
</Router>
</div>
)
}
useContext
import React, { useState , createContext } from 'react';
//===关键代码
const CountContext = createContext()
function Example4(){
const [ count , setCount ] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
{/*======关键代码 */}
<CountContext.Provider value={count}>
</CountContext.Provider>
</div>
)
}
export default Example4;
已经有了上下文变量,剩下的就时如何接收了,接收这个直接使用useContext
就可以,但是在使用前需要新进行引入useContext
(不引入是没办法使用的)。
import React, { useState , createContext , useContext } from 'react';
function Counter(){
const count = useContext(CountContext) //一句话就可以得到count
return (<h2>{count}</h2>)
}
useReducer
useReducer是React hooks提供的函数,可以增强我们的Reducer
,实现类似Redux的功能。
import React, { useReducer } from 'react';
function ReducerDemo(){
const [ count , dispatch ] =useReducer((state,action)=>{
switch(action){
case 'add':
return state+1
case 'sub':
return state-1
default:
return state
}
},0)
return (
<div>
<h2>现在的分数是{count}</h2>
<button onClick={()=>dispatch('add')}>Increment</button>
<button onClick={()=>dispatch('sub')}>Decrement</button>
</div>
)
}
export default ReducerDemo
这段代码是useReducer的最简单实现了,这时候可以在浏览器中实现了计数器的增加减少。
userReducer + userContext 实现redux效果
使用useContext
和useReducer
是可以实现类似Redux
的效果,并且一些简单的个人项目,完全可以用下面的方案代替Redux,这种做法要比Redux简单一些。
demo1:简单加减
父级组件
import React, { useEffect, useReducer } from 'react'
export const sateContext = React.createContext() // 创建并导出context
export default function Home (props) {
const [ state, dispatch ] = useReducer(stateReducer,0) // 创建reducer
function stateReducer (state, action) { // reducer处理函数
switch (action.type) {
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
return(
<sateContext.Provider value={{state,dispatch}}> // 传入需要共享的数据和reducer,需要注意传参的方式,json格式和直接传入
<Child />
</sateContext.Provider>
)
}
子组件代码 :
import React, { useContext } from 'react'
import {sateContext} from '../Home' // 导入父组件创建的context
import { Button } from 'antd'
export default function Child () {
const {state,dispatch} = useContext(sateContext) // 通过usecontext 接收共享数据
return (
<div>
{state}
<Button onClick={() => {dispatch({type:'add'})}}>加</Button> // 调用dispatch
<Button onClick={() => {dispatch({type:'sub'})}}>减</Button>
</div>
)
}
demo2: 修改颜色
Ⅰ、开始
首先用 create-react-app 创建一个项目
Ⅱ、创建颜色展示组件 ShowArea
import React from 'react'
const ShowArea = (props) => {
return (
<div style={{color: "blue"}}>字体颜色展示为blue</div>
)
}
export default ShowArea
Ⅲ、创建按钮组件 buttons
import React from "react";
const Buttons = props => {
return (
<div>
<button>红色</button>
<button>黄色</button>
</div>
);
};
export default Buttons;
Ⅳ、将 ShowArea 和 Buttons 导入 index.js(等会写出color状态组件后将会改写)
import React from "react";
import ReactDOM from "react-dom";
import ShowArea from './ShowArea'
import Buttons from './Buttons'
function App() {
return (
<div className="App">
<ShowArea />
<Button />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Ⅴ、状态管理
组件创建编写
很明显 ShowArea 组件需要一个颜色状态,所以我们创建 color.js 来处理状态。
{props.children}
传值给子组件(多个)
// color.js
import React, { createContext } from "react";
// 创建 context
export const ColorContext = createContext({});
/**
* 创建一个 Color 组件
* Color 组件包裹的所有子组件都可以通过调用 ColorContext 访问到 value
*/
export const Color = props => {
return (
<ColorContext.Provider value={{ color: "blue" }}>
{props.children}
</ColorContext.Provider>
);
};
Ⅵ、引入状态
修改 index.js,让所有子组件都可以访问到颜色。
// index.js
import { Color } from "./color";
function App() {
return (
<div className="App">
<Color>
<ShowArea />
<Buttons />
</Color>
</div>
);
}
Ⅶ、获取状态
在 ShowArea 组件中获取颜色 直接引入 然后使用
useContext
进行获取使用
// ShowArea.js
import { ColorContext } from "./color";
const ShowArea = props => {
const { color } = useContext(ColorContext);
return <div style={{ color: color }}>字体颜色展示为{color}</div>;
};
Ⅷ、创建 reducer
接着在 color.js 中添加 reducer, 用于处理颜色更新的逻辑。 然后使用useReducer
传入reducer进行处理
import React, { createContext, useReducer } from "react";
// 创建 context
export const ColorContext = createContext({});
//声明常量是方便使用
export const UPDATE_COLOR = "UPDATE_COLOR"
// reducer方法 声明
const reducer = (state, action) => {
switch(action.type) {
case UPDATE_COLOR:
return action.color
default:
return state
}
}
/**
* 创建一个 Color 组件
* Color 组件包裹的所有组件都可以访问到 value
*/
export const Color = props => {
const [color, dispatch] = useReducer(reducer, 'blue')
return (
<ColorContext.Provider value={{color, dispatch}}>
{props.children}
</ColorContext.Provider>
);
};
Ⅸ、更新状态
为按钮添加点击事件,调用 dispatch 就可以更新颜色了。
// buttons.js
import React, { useContext } from "react";
import { colorContext, UPDATE_COLOR } from "./color";
const Buttons = props => {
const { dispatch } = useContext(colorContext);
return (
<React.Fragment>
<button
onClick={() => {
dispatch({ type: UPDATE_COLOR, color: "red" });
}}
>
红色
</button>
<button
onClick={() => {
dispatch({ type: UPDATE_COLOR, color: "yellow" });
}}
>
黄色
</button>
</React.Fragment>
);
};
export default Buttons;
总结
- useContext 创建全局状态,不用一层一层的传递状态。
- useReducer 创建 reducer 根据不同的 dispatch 更新 state。
- 代码写到哪里状态就加到哪里,不用打断思路跳到 redux 里面去写。
- 全局状态分离,避免项目变大导致 Redux 状态树难以管理。
所以 useContext + useReducer 完全可以替代 React 进行状态管理。但是目前绝大多数 React 项目仍在使用 Redux,所以深入学习 Redux 还是很有必要的。
useMemo
useMemo
主要用来解决使用React hooks产生的无用渲染的性能问题。使用function的形式来声明组件,失去了shouldCompnentUpdate
(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分mount
和update
两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。
useMemo
和useCallback
都是解决上述性能问题的。
- useMemo缓存一个值,即return的那个,当组件依赖变化时,它的依赖没变换就不会重新执行
- 一般配合usecallback使用
import React, { useState, useMemo } from 'react';
// , PureComponent, memo, useState, useMemo, useCallback
import './App.css';
function Count(props) {
return <div>子组件count值:{props.count}</div>
}
function App(props) {
const [count, setCount] = useState(0);
//第一个参数是要执行的函数
//第二个参数是执行函数依赖的变量组成的数据
//这里只有count发生变化double才会重新计算
const double = useMemo(() => {
return count * 2;
}, [count])
return (
<div className="app">
<p>父组件count值:{count}</p>
<Count count={count} />
<button
onClick={() => {
setCount(count + 1)
}}>
Add
</button>
<div>double值:{double}</div>
</div>
)
}
export default App;
如果useMemo
返回的是一个函数,则可以用useCallback
省略顶层的函数。 将包裹的onClick函数用useCallback
包裹:
// const onClick = () =>{
// console.log('click')
// }
//这样返回的函数就会在组件重渲染时产生相同的句柄
//useMemo使用
// const onClick = useMemo(() => {
// //这里返回的依然是函数
// return () => {
// console.log('click')
// }
// }, []);
//useCallback使用
const onClick = useCallback(() => {
console.log('click')
}, []);
因此useCallback
是useMemo
的变体。
useMemo(()=>return fn);
//等价
useCallback(fn);
useCallback
useCallback
的作用其实是用来避免子组件不必要的reRender。
useCallback的用法与useState的用法基本一致,但最后会返回一个函数,用一个变量保存起来。
返回的函数a会根据b的变化而变化,如果b始终未发生变化,a也不会重新生成,避免函数在不必要的情况下更新。记得子组件导出时使用memo包裹一下,其作用是对组件前后两次进行浅对比,阻止不必要的更新。
用法:父子组件函数式传参
既然使用useCallback减少了函数式参数不必要的更新,子组件收到的参数不变,自然也不会更新,从而减少了组件间不必要的更新。
import React, { useState, useCallback, useEffect } from 'react';
const set = new Set();
export default function Parent() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
console.log(count);
return count
}, [count]); //count更新时执行
set.add(callback); //借助ES6新增的数据类型Set来判断当依赖count变更时是否返回新的函数
console.log(set);
return <div>
<h4>parent count:{count}</h4>
<h4>set size:{set.size}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
<Child callback={callback} />
</div>;
}
const Child = ({callback}) =>{
const [count,setCount] = useState(0);
useEffect(()=>{
setCount(callback())
},[callback]) //当callback更新时执行callback函数,得到parent组件最新的count
return <>
child count:{count}
</>
}
我们可以看到,每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。
实用场景
有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
useMemo + useCallback优化渲染性能
useMemo
和useCallback
接收的参数都是⼀样,第⼀个参数为回调,第⼆个参数为要依赖的数据。
共同作⽤
- 仅仅 依赖数据 发⽣变化, 才会重新计算结果,也就是起到缓存的作⽤。
两者区别
-
useMemo 计算结果是 return 回来的值, 主要⽤于 缓存计算结果的值 ,应⽤场景如: 需要 计算的状态
-
useCallback 计算结果是 函数, 主要⽤于 缓存函数,应⽤场景如: 需要缓存的函数,因为函数式组件每次任何⼀个 state 的变化 整个组件 都会被重新刷新,⼀些函数是没有必要被重新刷新的,此时就应该缓存起来,提⾼性能,和减少资源浪费。
应用场景
比如说父组件有一个筛选组件和一个table组件,筛选组件只依赖筛选条件filterOption,table组件只依赖dataSource。
如果dataSource变了,父组件就要更新,但这时其实筛选组件是不需要更新的 。
这时候,查询函数要定义在父组件中,要把它usecallback起来,再传给filter组件,filter用useMemo包起来 筛选条件作为依赖。
useRef
- 用
useRef
获取React JSX中的DOM元素,获取后你就可以控制DOM的任何东西了。但是一般不建议这样来作,React界面的变化可以通过状态来控制。 - 用
useRef
来保存变量(有了useContext
这样的保存其实意义不大)
useRef获取DOM元素
import React, { useRef} from 'react';
function Example8(){
const inputEl = useRef(null)
const onButtonClick=()=>{
inputEl.current.value="Hello ,JSPang"
console.log(inputEl) //输出获取到的DOM节点
}
return (
<>
{/*保存input的ref到inputEl */}
<input ref={inputEl} type="text"/>
<button onClick = {onButtonClick}>在input上展示文字</button>
</>
)
}
export default Example8
useRef保存普通变量
import React, { useRef ,useState,useEffect } from 'react';
function Example8(){
const inputEl = useRef(null)
const onButtonClick=()=>{
inputEl.current.value="Hello ,useRef"
console.log(inputEl)
}
//-----------关键代码--------start
const [text, setText] = useState('jspang')
const textRef = useRef()
useEffect(()=>{
textRef.current = text;
console.log('textRef.current:', textRef.current)
})
//----------关键代码--------------end
return (
<>
{/*保存input的ref到inputEl */}
<input ref={inputEl} type="text"/>
<button onClick = {onButtonClick}>在input上展示文字</button>
<br/>
<br/>
<input value={text} onChange={(e)=>{setText(e.target.value)}} />
</>
)
}
export default Example8
这时候就可以实现每次状态修改,同时保存到useRef
中了。
自定义hook
一些常见的自定义hook,用于满足业务功能
useWinSize:获取浏览器窗口尺寸
在实际开发中,为了界面更加美观。获取浏览器窗口的尺寸是一个经常使用的功能,这样经常使用的功能,就可以封装成一个自定义Hooks
函数,记住一定要用use开头,这样才能区分出什么是组件,什么是自定义函数。
新建一个文件Example9.js
,然后编写一个useWinSize,编写时我们会用到useState
、useEffect
和useCallback
所以先用import
进行引入。
import React, { useState ,useEffect ,useCallback } from 'react';
function useWinSize(){
const [ size , setSize] = useState({
width:document.documentElement.clientWidth,
height:document.documentElement.clientHeight
})
const onResize = useCallback(()=>{
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
})
},[])
useEffect(()=>{
window.addEventListener('resize',onResize)
return ()=>{
window.removeEventListener('resize',onResize)
}
},[])
return size;
}
调用
function Example9(){
const size = useWinSize()
return (
<div>页面Size:{size.width}x{size.height}</div>
)
}
export default Example9
useUserContext:自定义Context的共享
Context的共享
首先我们在APP.js中定义两个context
export const UserContext = createContext()
export const TokenContext = createContext()
<UserContext.Provider value={{name:'lsh'}}>
<TokenContext.Provider value={{token:'asd'}}>
<ContextShare/>
</TokenContext.Provider>
</UserContext.Provider>
user-hook.js
将UserContext,TokenContext导入,然后通过useContext来赋值给我们定义的变量,然后给它return出去,这样我们在不同的组件也都能获取到
import React, { useContext } from 'react'
import {UserContext,TokenContext} from '../App'
export default function useUserContext() {
const user = useContext(UserContext)
const token = useContext(TokenContext)
return [user,token]
}
之后在其他页面怎么用呢?
import React, { useContext } from 'react'
import useUserContext from '../hook/user-hook'
export default function ContextShare() {
// 核心代码
const [user,token] = useUserContext()
console.log(user,token)
return (
<div>
</div>
)
}
useScrollPostion:获取页面滚动位置
scroll-postion.js
import React,{useState,useEffect} from 'react'
export default function useScrollPostion() {
const [scrollPosition, setScrollPostion] = useState(0)
useEffect(() => {
const handleScroll = () => {
setScrollPostion(window.scrollY)
}
document.addEventListener('scroll', handleScroll);
return () => {
document.removeEventListener('scroll', handleScroll)
}
},[])
return [scrollPosition]
return (
<div>
</div>
)
}
调用
import React, { useEffect, useState } from 'react'
import useScrollPostion from '../hook/scroll-postion'
export default function CustomScroll() {
const position = useScrollPostion()
return (
<div style={{ padding: '1000px 0 ' }}>
gogoing
<h2>{position}</h2>
</div>
)
}
useLocalStorage:localStorage的存储
local-storage-hook.js
import React,{ useState, useEffect } from 'react'
export default function useLocalstoragehook(key) {
const [name, setName] = useState(() => {
const name = JSON.parse(localStorage.getItem(key))
return name
})
useEffect(() => {
// localStorage.setItem('name', name)
localStorage.setItem(key, JSON.stringify(name))
}, [name])
return [name,setName]
return (
<div>
</div>
)
}
组件调用
import React, { useState, useEffect } from 'react'
import useLocalstoagehook from '../hook/local-storage-hook'
export default function CustomLocalStorage() {
const [name,setName]= useLocalstoagehook('name')
return (
<div>
{name}
<button onClick={e => setName('lsh')}>设置</button>
</div>
)
}
usePersistFn:返回一个永远不变的函数引用
接收一个函数,返回一个永远不变的函数引用,在这个函数中每次都能拿到最新的state值
function usePersistFn(fn) {
const fnRef = useRef(fn);
fnRef.current = fn;
const persistFn = useRef();
if (!persistFn.current) {
persistFn.current = function (...args) {
return fnRef.current.apply(this, args);
};
}
return persistFn.crrent;
}
参考
https://blog.csdn.net/u013565133/article/details/104001901
https://jspang.com/article/50
《react hooks 中useCallback的作用,实例讲解》https://blog.csdn.net/sinat_36146776/article/details/110549211