Hook官网 react.docschina.org/docs/hooks-…
useState
- useState的快速使用:
- 1.从 Raect 导入 useState 函数
- 2.执行这个函数并且传入初始值,必须在函数组件中使用
- 3.[数据,修改数据的方法]
- 4.使用数据,修改数据
import React,{useState} from 'react';
function App() {
const [count,setCount] = useState(0);
return (
<div className="App">
this is app...
<span>{count}</span>
<button onClick={()=>setCount(count+10)}>修改count</button>
</div>
);
}
export default App;
2.useState状态的读取和修改:
- const [count,setCount] = useState(0);
- 1.useState传过来的参数0,作为 count 的初始值,这个初始值只在第一次执行的时候生效。
- 2.[count,setCount] 这里的写法是一个解构赋值,useState的返回值是一个数组。
- count,setCount名字可以自定义,但是需要保证语义化。
- count,setCount名字顺序不可以换,第一个参数就是数据状态,第二个参数就是修改数据的方法。(对象的解构赋值是无序的,通过key,value的形式取。数组的解构赋值是有序的。)
- 3.setCount 函数,作用是用来修改count ,依旧保持不能直接修改原值,还是生成一个新值替换原值。
- setCount是基于原值计算得到的新值。
- 4.count和setCount是一对,是绑定在一起的,setCount只能用来修改对应的count值。
import React,{useState} from 'react';
function App() {
const [count,setCount] = useState(0);
return (
<div className="App">
this is app...
<span>{count}</span>
<button onClick={()=>setCount(count+10)}>修改count</button>
</div>
);
}
export default App;
3.组件的更新过程(当调用setCount的时候,更新过程):
- 1.首次渲染
- 首次渲染的时候,App组件内部的代码会被执行一次,其中useState也会跟着执行,这里重点注意:初始值 useState(0) 只在首次渲染时生效。
- 2.更新渲染(只要调用setCount都会更新)
- App组件会再次渲染,setCount这个函数会再次执行
- useState再次执行,得到的新的count值不是0而是修改之后的1,模板会用新值渲染。
- 小结:useState初始值只在首次渲染生效,后续只要调用setCount,整个App组件中的代码都会执行,此时的count每次拿到的都是最新值。
import React,{useState} from 'react';
function App() {
const [count,setCount] = useState(0);
console.log(count);// 更新渲染的时候,每次都是count的最新值
return (
<div className="App">
this is app...
<span>{count}</span>
<button onClick={()=>setCount(count+10)}>修改count</button>
</div>
);
}
export default App;
4.使用react_dev_tools查看最新的useState定义的状态值
5.useState的使用规则:
- 1.useState函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态。(App组件可以通过useState定义多个初始值的状态)
- 2.useState只能出现在App组件里面,不能if/for/其他函数中
- 3.可以通过react_dev_tools开发者工具查看hook状态
import React,{useState} from 'react';
function App() {
const [count,setCount] = useState(0);
console.log(count);// 更新渲染的时候,每次都是count的最新值
const [flag,setFlag] = useState(true);
const [list,setList] = useState([]);
const test = () =>{
setCount(count+10);
setFlag(false);
setList([1,2,3])
}
return (
<div className="App">
this is app...
<div>count:{count}</div>
<div>flag:{flag?'1':'0'}</div>
<div>list:{list.join('-')}</div>
<button onClick={()=>test()}>修改count</button>
</div>
);
}
export default App;
- 将一个回调函数作为useState的参数的使用场景:
- 参数只会在组件初始化渲染的时候起作用,后续渲染时会被忽略。
- 如果初始化的state需要通过计算才能获得,则可以传入一个回调函数,在函数中计算并返回初始值的state,此函数只在初始化渲染时被调用。
- 需求:如何实现一个自增按钮,可以由使用的时候以传参的方式决定递增的初始值?
import React,{useState} from 'react';
function Counter(props){
const [count,setCount] = useState(()=>{
/*
这里的目的是为了体现初始值经过一定的计算,但是这个计算是一个比较广义的概念。
只要无法直接确定,需要通过一定的操作才能获取,就可以理解为计算。
*/
// 编写计算逻辑,return '计算之后的初始值'
return props.count
});
const handle = () =>{
setCount(count+10)
}
return (
<button onClick={handle}>{count}</button>
)
}
function App(){
return (
<div>
<Counter count={10}/>
<Counter count={22}/>
</div>
)
}
export default App;
- useState将回调函数作为参数示例
import React,{useState} from 'react';
function getDefaultValue(){
for(let i=0;i<10000;i++){
}
return 10
}
function Counter(props){
const [count,setCount] = useState(()=>{
/*
这里的目的是为了体现初始值经过一定的计算,但是这个计算是一个比较广义的概念。
只要无法直接确定,需要通过一定的操作才能获取,就可以理解为计算。
*/
// 编写计算逻辑,return '计算之后的初始值'
// return props.count
return getDefaultValue()
});
const handle = () =>{
setCount(count+10)
}
return (
<button onClick={handle}>{count}</button>
)
}
function App(){
return (
<div>
<Counter/>
</div>
)
}
export default App;
useEffect
useEffect常见的副作用:
- 1.数据请求。
- 2.手动修改DOM。
- 3.localstorage操作。 1.useEffect的简单使用(需求:在修改数据之后,把count值放到页面标题中。)
- 1.导入useEffect函数。
- 2.在函数组件中执行,传入回调,并且定义副作用。
- 3.当我们通过修改状态更新组件时,副作用也会不断执行。
import React,{useEffect, useState} from 'react';
function App(){
const [count,setCount] = useState(0);
const handle = () =>{
setCount(count+10)
}
useEffect(()=>{
document.title = count
})
return (
<div>
this is App 。。。
<div>
<button onClick={handle}>{count}</button>
</div>
</div>
)
}
export default App;
2.通过依赖项控制副作用的执行时机:
- 1.默认状态(无依赖项)
- 组件初始化的时候先执行一次,等到每次数据修改组件更新时再次执行。
- 2.添加一个空数组依赖项
- 只会在组件初始化的时候执行一次。
- 3.依赖特定项
- 组件初始化的时候执行一次,依赖的特定项发生变化会再次执行。
- 4.注意事项:
- 只要在useEffect回调函数中用到的数据状态,就应该出现在依赖项数组中声明,否则可能会有bug。
- 某种意义上,hook的出现,就是想不用类组件的生命周期的概念,也可以写业务代码。
import React,{useEffect, useState} from 'react';
function App(){
const [count,setCount] = useState(0);
const [name,setName] = useState('xyh');
const handleCount = () =>{
setCount(count+10)
}
const handleName = () =>{
setName('jhh')
}
// 1.默认无依赖项
// useEffect(()=>{
// console.log('操作DOM');
// //定义副作用
// document.title = count
// })
// 2.添加空数组为依赖项
// useEffect(()=>{
// console.log("依赖项是空数组");
// document.title = 'useEffect'
// },[])
// 3.特定依赖项
// useEffect(()=>{
// console.log('特定依赖项');
// document.title=count
// },[count])
//4.注意事项:例如:回调函数中使用了name,就应该把name定义到依赖项里面。
useEffect(()=>{
console.log('特定依赖项');
document.title=count
console.log(name)
},[count,name]); // 该副作用函数什么时候会执行?初始化的时候执行一次,当count/name被修改时都会在执行一次。
return (
<div>
this is App 。。。
<div>
<button onClick={handleCount}>{count}</button>
<button onClick={handleName}>{name}</button>
</div>
</div>
)
}
export default App;
- useEffect清理副作用(componentWillUnmount)的使用场景:
- 在组件被销毁时,有些副作用操作需要被清理,例如:定时器
import React,{useState,useEffect} from 'react';
function Test(){
useEffect(()=>{
let timer = setInterval(()=>{
console.log('定时器开始执行');
},1000);
// 清除定时器
return ()=>{
clearInterval(timer)
}
},[])// 依赖项为空数组,只会执行一次
return <div>xxx</div>
}
function App(){
const [flag,setFlag] = useState(true);
const handleFlag = () =>{
setFlag(!flag)
}
return (
<div>
{flag?<Test/>:null}
<button onClick={handleFlag}>switch</button>
</div>
)
}
export default App;
4.useEffect发送网络请求
- 类组件如何发送网络请求?
- 通过componentDidMount生命周期钩子函数。
- componentDidMount生命周期的执行时机?
- 在初始化的时候,DOM渲染完毕只执行一次。 useEffect:
- 1.不加依赖项,初始化渲染和状态更新都会执行
- 2.加 [] , 只会在初始化时执行一次。
- 3.加特定的依赖项[count,name],初始化时执行和count、name任意一个变化执行
import React,{useEffect} from 'react';
function App(){
useEffect(()=>{
// 真实发送请求的写法:
// async function loadData(){
// const res = await fetch('http://geek.itheima.net/v1_0/channels');
// console.log(res);
// }
// loadData();
//查看学习Fetch API的写法:
function loadData(){
fetch('http://geek.itheima.net/v1_0/channels').then(
res=>res.json()
).then(data=>console.log(data))
}
loadData()
},[])
return (
<div>
this is app ...
</div>
)
}
export default App;
useRef
useRef:
- 作用:useRef可以获取真实DOM或组件实例。(组件实例是指类组件,DOM对象是标签)
- 使用场景:在函数组件中获取真实DOM元素对象或者是组件对象。
- 使用步骤:
- 1.导入useRef函数。
- 2.执行useRef函数并传入null,返回值为一个对象内部有一个current属性存放拿到的DOM对象(或者组件实例)
- 3.通过ref绑定要获取的元素或者组件。 注意:useEffect回调函数是在DOM渲染完成之后执行。
import React,{useEffect, useRef} from 'react';
// 组件实例是指类组件
class Test extends React.Component{
state = {
name:'我是类组件(组件实例)'
}
getName = () =>{
return 'this is classTest'
}
render(){
return (
<div>
{this.state.name}
</div>
)
}
}
function App(){
const testRef = useRef(null);
const hRef = useRef(null);
// useEffect回调函数是在DOM渲染完成之后执行。
useEffect(()=>{
console.log(testRef.current);
console.log(hRef.current);
},[])
return (
<div>
<Test ref={testRef}/>
<h1 ref={hRef}>this is h1...</h1>
</div>
)
}
export default App;
useContext
useContext的使用步骤:
- 1.使用createContext创建Context对象
- 2.在顶层组件通过Provider提供数据
- 3.在底层组件通过useContext函数获取数据
import React,{createContext,useContext, useState} from 'react';
const Context = createContext();//可以得到一个对象
function ComC(){
const count = useContext(Context)
return (
<div>
this is ComC。。。
App组件传入:{count}
</div>
)
}
function ComA(){
const count = useContext(Context);
return (
<div>
this is ComA。。。
App组件传入:{count}
<ComC/>
</div>
)
}
function App(){
const [count,setCount] = useState(1688);
// handCount修改状态
const handCount = () =>{
setCount(count+100)
}
return (
<Context.Provider value={count}>
<div>
<ComA/>
<button onClick={handCount}>改变count</button>
</div>
</Context.Provider>
)
}
export default App;
useMemo
- useMemo 记忆函数
const fn = useMemo(() => {
return () => { console.log('fn') }
}, []);
useCallback
- useCallback用来记忆函数的,当需要记忆的对象是一个函数的时候:
const fn = useCallback(() => {
console.log('fn')
}, []);
useReducer
useReducer 配合 useContext 可以替代 redux。
// 1.导入useReducer
import React, { useReducer } from "react";
// 2.创建初始值
const initial = {
n: 0
};
// 3.创建所有操作
const reducer = (state, action) => {
if (action.type === "add") {
return { n: state.n + action.number };
} else if (action.type === "multi") {
return { n: state.n * action.number };
} else {
throw new Error("unknown type");
}
};
function App() {
// 4.将reducer 和 initial 传给useReducer,得到读和写的api:state 和 dispatch
const [state, dispatch] = useReducer(reducer, initial);
const { n } = state;
const AddHandle = () => {
// 5-1.调用dispatch
dispatch({ type: "add", number: 1 });
};
const MultiHandle = () => {
// 5-2.调用dispatch
dispatch({ type: "multi", number: 2 });
};
return (
<div className="App">
<h1>n: {n}</h1>
<button onClick={AddHandle}>+1</button>
<button onClick={MultiHandle}>x2</button>
</div>
);
}
export default App;
forwardRef
useLayoutEffect
useImperativeHandle
自定义hook
- 提取组件逻辑,实现复用,优化代码。
- 案例一:
-
需求:自定义一个hook函数,实现获取滚动距离Y。
- const [y] = useWindowScroll(); // y就是滚动到顶部的距离
import React from 'react'; import {useWindowScroll} from './hooks/1.useWindowScroll' function App(){ const [y] = useWindowScroll(); return ( <div style={{height:'12000px'}}> this is App ... {y} </div> ) } export default App;import {useState} from 'react'; export function useWindowScroll(){ const [y,sety] = useState(0); // 实现思路:在滚动行为发生的时候,不断获取滚动值,然后交给y window.addEventListener('scroll',()=>{ const h = document.documentElement.scrollTop;//可以拿到当前顶部的距离 sety(h) }); return [y] }
-
- 案例二:
- 需求:自定义hook函数,可以自动同步到本地LocalStorage。
import React from 'react'; import {useLocalStorage} from './hooks/2.useLocalStorage'; function App(){ const [message,setMessage] = useLocalStorage('hook-key','小辉'); setTimeout(()=>{ setMessage("小明") },5000) return ( <div> {message} </div> ) } export default App;import React,{useState,useEffect} from 'react' export function useLocalStorage(key,defaultValue){// key是存储数据的,defaultValue是默认值 const [message,setMessage] = useState(defaultValue); // 每次只要message变化,就会自动同步到本地localStorage(加入useEffect副作用,本地存储) useEffect(()=>{ window.localStorage.setItem(key,message) },[message,key]) return [message,setMessage] }
- 需求:自定义hook函数,可以自动同步到本地LocalStorage。
1.useRef 保存数据和 useState 保存数据的区别:
-
- 与视图相关的请用 useState 保存,因为只有 setState 会驱动视图的更新,而改变 useRef() 对象是不会改变视图的。
-
- 保存的对象需要在生命周期中唯一(单例),请使用 useRef,因为 useState 每次更新会重新生成一个对象。
-
- 如果想改变某个值后,同步获取更新后的值,请使用 useRef,因为 useState 的改变不是同步的,要在下一次 render 后生效。
2.React.memo 和 useMemo
- 使用React.memo的时候,如果父组件传给子组件的是一个对象,父组件重新渲染后,父组件中要传给子组件的对象也会重新生成,空间地址也就和之前的不一样了,所以子组件也会重新渲染,此时React.memo的作用就失效了。
- 父组件如果传给子组件的是对象,React.memo失效的解决方法?
- 用 useMemo 记忆一下这个对象,使它的内存地址保持不变。
//☆useMemo 记忆对象props保持内存不变 import React, { useState, useMemo } from "react"; function App() { const [n, setN] = useState(0); const [m, setM] = useState(1000); const handleN = () => { setN((n) => n + 10); }; // const data2 = { a: 1 }; const data2 = useMemo(() => { return { age: 18 }; }, []); return ( <div className="App"> <button onClick={handleN}>update n {n}</button> <NewChild data={m} data2={data2} /> </div> ); } function Child(props) { console.log("child 执行了"); console.log("假设这里有大量代码"); return <div> child: {JSON.stringify(props.data2.age)} <div>{props.data}</div> </div>; } const NewChild = React.memo(Child); export default App;