useState
一个可以根据key 动态setFormData的例子
function Component() {
const [person, setPerson] = useState({ name: 'puxiao', age: 34 });
const valueChange = (key, e) => {
person[key] = e.target.value
setPerson({...person})
}
return <div>
<input type='text' value={person.name} onChange={e => valueChange('name', e)} />
<input type='number' value={person.age} onChange={e => valueChange('age', e)} />
{JSON.stringify(person)}
</div>
}
useState中修改对象如何操作
import React, { useState } from 'react';
function Component() {
const [str, setStr] = useState('');
const [arr, setArr] = useState(['react', 'Koa']);
const inputChangeHandler = (eve) => {
setStr(eve.target.value);
}
const addHeadHandler = (eve) => {
setArr([str,...arr]);//添加至头
setStr('');
}
const addEndHandler = (eve) => {
setArr([...arr, str]);//添加至尾
setStr('');
}
const delHeadHandler = (eve) => {
let new_arr = [...arr];
new_arr.shift();//从头删除1项目
setArr(new_arr);
}
const delEndHandler = (eve) => {
let new_arr = [...arr];
new_arr.pop();//从尾删除1项目
setArr(new_arr);
}
const delByIndex = (eve) => {
let index = eve.target.attributes.index.value;
let new_arr = [...arr];
new_arr.splice(index,1);//删除当前项
setArr(new_arr);
}
return <div>
<input type='text' value={str} onChange={inputChangeHandler} />
<button onClick={addHeadHandler} >添加至头</button>
<button onClick={addEndHandler} >添加至尾</button>
<button onClick={delHeadHandler} >从头删除1项</button>
<button onClick={delEndHandler} >从尾删除1项</button>
<ul>
{arr.map(
(item, index) => {
return <li key={`item${index}`}>学习{index} - {item}
<span index={index} onClick={delByIndex} style={{ cursor: 'pointer' }}>删除</span>
</li>
}
)}
</ul>
</div>
}
export default Component;
useState中修改深层次(大于三级嵌套的数组)对象如何操作
useCallback
不能做的优化
import React, { useState, useCallback } from 'react'
export default function UseCallbackDemoOne() {
const [count,setCount] = useState(0)
const increament1 = () => {
console.log('执行increament1')
setCount(count + 1)
}
const increament2 = useCallback(()=>{
console.log('执行increament2')
setCount(count + 1)
},[])
return (
<div>
<h2>{count}</h2>
<button onClick={increament1}>+1</button>
<button onClick={increament2}>+1</button>
</div>
)
}
如图increament1所示,不管怎么修改count,不需要加useCallback也能获取到最新的count。图二中useCallback会返回一个函数的memoized(记忆的)值。如果不加依赖,会一直拿到原本得值。
如果使用useCallback也没法优化性能,不会重新渲染吗?
答:还是会重新渲染,所以usecallback在这种单个组件不能优化性能。
可以优化的情况
import React, { useState, useCallback, memo } from 'react'
const ChildComp =
memo( // 这里的memo作用是减少子组件渲染。(前提是传递过来的是普通的props)
({
changeCount,
count }) => {
console.log('重渲染');
return <div>子组件
<button onClick={changeCount}>改变子组件的count</button>
<div>子组件的数据 count: {count}</div>
</div>
}
)
export default function UseCallbackDemoOne() {
const [count, setCount] = useState(0) // 有关的数据
const [name, setName] = useState(1) // 无关的数据
// 这里的useCallback作用是减少子组件渲染 (前提是将要传递给子组件的一个函数)
// 有没有useCallback的区别:usecallback如果不加,子组件每次都会渲染,而且前提是子组件也得加上memo。
const changeCount = useCallback(() => {
setCount(value => value + 1)
}, [])
return <div>
父组件
count: {count}
name: {name}
<button onClick={() => setName(name + 1)}>name + 1</button>
<hr />
子组件
<ChildComp
changeCount={changeCount}
count={count} />
<ChildComp
changeCount={changeCount}
count={count} />
</div>
}
如果传递给子组件的事件去修改了父组件的数据,并且使用memo和useCallback,并不会触发无关的渲染。可以优化性能。如下所示:
import React, { useState, useCallback, memo } from 'react'
// 这个memo很重要,可以减少子组件不必要的渲染。
const ChildComp = memo(({ setCountFun, setNameFun }) => {
console.log('子组件重渲染');
return <div>子组件
<button onClick={setCountFun}>setCountFun</button>
</div>
})
export default function UseCallbackDemoOne() {
const [count, setCount] = useState(0)
const [name, setName] = useState(0)
const setCountFun = useCallback(() => {
setCount(count + 1)
}, [count])
const setNameFun = useCallback(() => {
setName(count + 1)
}, [])
return (
<div>
<h2>{count}</h2>
<h2>{name}</h2>
<button onClick={() => setCount(count + 1)}>count + 1</button>
<button onClick={() => setName(name + 1)}>name + 1</button>
<button onClick={() => setCountFun()}>setCountFun</button>
{/* {console.log('重新渲染')} */}
<ChildComp setCountFun={setCountFun} />
<ChildComp setNameFun={setNameFun} />
</div>
)
}
useMemo
import React, { useState, useCallback, memo, useMemo } from 'react'
// 这个memo很重要,可以减少子组件不必要的渲染。
const ChildComp = memo(({ obj }) => {
console.log('子组件重渲染');
return <div>子组件
{/* <button onClick={setCountFun}>setCountFun</button> */}
</div>
})
export default function UseCallbackDemoOne() {
const [count, setCount] = useState(0)
const [name, setName] = useState(0)
const [obj, setObj] = useState({
age: 20,
name: 'glack'
})
const setCountFun = useCallback(() => {
setCount(count + 1)
}, [count])
const setNameFun = useCallback(() => {
setName(count + 1)
}, [])
return (
<div>
<h2>{count}</h2>
<h2>{name}</h2>
<button onClick={() => setCount(count + 1)}>count + 1</button>
<button onClick={() => setName(name + 1)}>name + 1</button>
<ChildComp obj={useMemo(() => ({obj, test: 'tetst'}), [])}/>
// <ChildComp obj={{obj, test: 'tetst'}}/> 如果传递的属性是一个对象,并且不加useMomo会导致子组件多出无用的渲染,修改count,但是子组件没有依赖count,也会造成渲染。
</div>
)
}
useMemo和useCallback类似,一个是可以缓存函数(useCallback),一个是可以缓存属性(useMemo)。在值有所依赖的项,并且是对象和数组等值的时候而使用useMemo(当返回的是原始数据类型如字符串、数字、布尔值,就不要使用useMemo了)。不要盲目使用这些hooks。
补充说明:
1、useMemo并不需要子组件必须使用React.memo。
2、“不必要的函数计算”中的函数计算必须是有一定复杂度的,例如需要1000个for循环才能计算出的某个值。如果计算量本身很简单,例如1+2,那完全没有必要使用useMemo,就直接每次重新计算一遍也无所谓。
不需要子组件必须使用React.memo,例子:
import { useMemo, useState } from "react"
export default function IndexPage() {
const [name, setName] = useState('glack')
const [age, setAge] = useState(10)
const desc = useMemo(() => {
console.log('desc重新执行');
return {
age: 1,
desc: '我是glack,年龄是' + age
}
}, [age])
// const desc = () => {
// 如果如此使用,修改name将会执行一帧。
// for 1000 .
// console.log('desc重新执行');
// return {
// age: 1,
// desc: '我是glack,年龄是' + age
// }
// }
return (<div>
name: {name} <br />
age: {age} <br />
desc: {JSON.stringify(desc)} <br />
{/* desc: {JSON.stringify(desc())} <br /> */}
<button onClick={() => setName(name + 1)}>name + 1</button> <br />
<button onClick={() => setAge(age + 1)}>age + 1</button> <br />
</div>)
}
useContext简单使用
import GlobalContext from './global-context'; //引入共享数据对象
function Component(){
const global = useContext(GlobalContext); //在函数组件中声明一个变量来代表该共享数据对象的value值
//若想获取共享数据对象中的属性xxx的值,直接使用global.xxx即可
return <div>
{global.xxx}
</div>
}
// global-context.js
import React from 'react';
const GlobalContext = React.createContext({
name: 'glack',
age: 20
});
//请注意,这里还可以给React.createContext()传入一个默认值
//例如:const GlobalContext = React.createContext({name:'Yang',age:18})
//假如<GlobalContext.Provider>中没有设置value的值,就会使用上面定义的默认值
export default GlobalContext;
父组件传递context给子组件
import React,{ useContext } from 'react'
const UserContext = React.createContext();
const NewsContext = React.createContext();
function AppComponent() {
return (
<UserContext.Provider value={{name:'puxiao'}}>
<NewsContext.Provider value={{title:'Hello React Hook.'}}>
<ChildComponent />
</NewsContext.Provider>
</UserContext.Provider>
)
}
function ChildComponent(){
const user = useContext(UserContext);
const news = useContext(NewsContext);
return <div>
{user.name} - {news.title}
</div>
}
export default AppComponent;
主要用到了XxxContext.Provider这个api
为什么不使用Redux?
在Hook出现以前,React主要负责视图层的渲染,并不负责组件数据状态管理,所以才有了第三方Redux模块,专门来负责React的数据管理。
但是自从有了Hook后,使用React Hook 进行函数组件开发,实现数据状态管理变得切实可行。只要根据实际项目需求,使用useContext以及下一章节要学习的useReducer,一定程度上是可以满足常见需求的。
毕竟使用Redux会增大项目复杂度,此外还要花费学习Redux成本。
具体需求具体分析,不必过分追求Redux。
useReducer
在React 16.8版本以前,通常需要使用第三方Redux来管理React的公共数据,但自从 React Hook 概念出现以后,可以使用 useContext + useReducer 轻松实现 Redux 相似功能。这一部分会在 “useReducer高级用法” 中做详细讲解。
补充说明:
1、在React源码中,实际上useState就是由useReducer实现的,所以useReducer准确来说是useState的原始版。
2、无论哪一个Hook函数,本质上都是通过事件驱动来实现视图层更新的。
// 一个更改计数器的例子
import React, { useReducer } from 'react';
// 这里的reducer和redux的reducer写法一致。
function reducer(state,action){
switch(action.type){
case 'add':
return state + action.param;
case 'sub':
return state - action.param;
case 'mul':
return state * action.param;
default:
console.log('what?');
return state;
}
}
function getRandom(){
return Math.floor(Math.random()*10);
}
function CountComponent() {
const [count, dispatch] = useReducer(reducer,0);
return <div>
{count}
<button onClick={() => {dispatch({type:'add',param:getRandom()})}} >add</button>
<button onClick={() => {dispatch({type:'sub',param:getRandom()})}} >sub</button>
<button onClick={() => {dispatch({type:'mul',param:getRandom()})}} >mul</button>
</div>;
}
export default CountComponent;
使用useReducer来管理复杂类型的数据
const initralData = {loading: true,result: '',error: false};
const reducer = (state, action) => {
switch (action.type) {
case 'succes':
return {loading:false,result:action.res,error:false}
case 'error':
return {loading:false,error:true}
}
}
function Component() {
const [state, dispatch] = useReducer(reducer, initralData);
{
//ajax请求成功
dispatch({type:'succes',res:'You have a good news!'});
//ajax请求错误
dispatch({type:'error'});
}
return <div>
{state.loading ? 'loading...' : state.result}
{state.error ? 'wrong!' : null}
</div>
}
使用 useContext + useReducer 轻松实现 Redux 相似功能
共享对象 代码如下:
import React from 'react';
const CountContext = React.createContext();
export default CountContext;
父组件代码
import React, { useReducer } from 'react';
import CountContext from './CountContext';
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
import ComponentC from './ComponentC';
const initialCount = 0; //定义count的默认值
//修改count事件处理函数,根据修改参数进行处理
function reducer(state, action) {
//注意这里先判断事件类型,然后结合携带的参数param 来最终修改count
switch (action.type) {
case 'add':
return state + action.param;
case 'sub':
return state - action.param;
case 'mul':
return state * action.param;
case 'reset':
return initialCount;
default:
console.log('what?');
return state;
}
}
function ParentComponent() {
//定义全局变量count,以及负责抛出修改事件的dispatch
const [count, dispatch] = useReducer(reducer, initialCount);
//请注意:value={{count,dispatch} 是整个代码的核心,把将count、dispatch暴露给所有子组件
return <CountContext.Provider value={{count,dispatch}}>
<div>
ParentComponent - count={count}
<ComponentA />
<ComponentB />
<ComponentC />
</div>
</CountContext.Provider>
}
export default ParentComponent;
子组件代码
import React,{ useState, useContext } from 'react';
import CountContext from './CountContext';
function CopmpoentA() {
const [param,setParam] = useState(1);
//引入全局共享对象,获取全局变量count,以及修改count对应的dispatch
const countContext = useContext(CountContext);
const inputChangeHandler = (eve) => {
setParam(eve.target.value);
}
const doHandler = () => {
//若想修改全局count,先获取count对应的修改抛出事件对象dispatch,然后通过dispatch将修改内容抛出
//抛出的修改内容为:{type:'add',param:xxx},即告诉count的修改事件处理函数,本次修改的类型为add,参数是param
//这里的add和param完全是根据自己实际需求自己定义的
countContext.dispatch({type:'add',param:Number(param)});
}
const resetHandler = () => {
countContext.dispatch({type:'reset'});
}
return <div>
ComponentA - count={countContext.count}
<input type='number' value={param} onChange={inputChangeHandler} />
<button onClick={doHandler}>add {param}</button>
<button onClick={resetHandler}>reset</button>
</div>
}
export default CopmpoentA;
useRef
基础使用
import React,{useEffect,useRef} from 'react'
function Component() {
//先定义一个inputRef引用变量,用于“勾住”挂载网页后的输入框
const inputRef = useRef(null);
useEffect(() => {
//inputRef.current就是挂载到网页后的那个输入框,一个真实DOM,因此可以调用html中的方法focus()
inputRef.current.focus();
},[]);
return <div>
{/* 通过 ref 属性将 inputRef与该输入框进行“挂钩” */}
<input type='text' ref={inputRef} />
</div>
}
export default Component
useRef 配合定时器使用
import React,{useState,useEffect,useRef} from 'react'
function Component() {
const [count,setCount] = useState(0);
const timerRef = useRef(null);//先定义一个timerRef引用变量,用于“勾住”useEffect中通过setIntervale创建的计时器
useEffect(() => {
//将timerRef.current与setIntervale创建的计时器进行“挂钩”
timerRef.current = setInterval(() => {
setCount((prevData) => { return prevData +1});
}, 1000);
return () => {
//通过timerRef.current,清除掉计时器
clearInterval(timerRef.current);
}
},[]);
const clickHandler = () => {
//通过timerRef.current,清除掉计时器
clearInterval(timerRef.current);
};
return (
<div>
{count}
<button onClick={clickHandler} >stop</button>
</div>
)
}
export default Component
两种实现方式对比:
1、两种实现方式最主要的差异地方在于 如何创建组件内对计时器的引用。
2、两种创建引用的方式,分别是:用useState创建的timer、用useRef创建的timerRef
3、在使用setInterval时,相对来说timerRef.current更加好用简单,结构清晰,不需要像 setTimer那样需要再多1层包裹。
4、timer更像是一种react对计时器的映射,而timerRef直接就是真实DOM中计时器的引用,timerRef能够调用更多的原生html中的JS方法和属性。
结论:
1、如果需要对渲染后的DOM节点进行操作,必须使用useRef。
2、如果需要对渲染后才会存在的变量对象进行某些操作,建议使用useRef。
第3遍强调:useRef只适合“勾住”小写开头的类似原生标签的组件。如果是自定义的react组件(自定义的组件必须大写字母开头),那么是无法使用useRef的。
父组件调用子组件中的函数
ParentComponent
import { useRef } from "react";
import ChildComponent from "./child";
const ParentComponent = () => {
const childFunRef = useRef();
const handleOnClick = () => {
if (childFunRef.current) {
childFunRef.current.doSomething();
}
};
return (
<div>
<ChildComponent funRef={childFunRef} />
<button onClick={handleOnClick}>执行子项的doSomething()</button>
</div>
);
};
export default ParentComponent;
ChildComponent
import { useEffect, useState } from "react";
const ChildComponent = ({ funRef }) => {
const [num, setNum] = useState(0);
useEffect(() => {
const doSomething = () => {
setNum(Math.floor(Math.random() * 100));
};
funRef.current = { doSomething }; //在子组件中修改父组件中定义的childFunRef的值
}, [funRef]);
return <div>{num}</div>;
};
export default ChildComponent;
附件:一个大佬写的总结