useState
function App() {
const [name,setName] = useState("name");
return <div>
<p>{name}</p>
<button onClick={()=>{
setName("显示名称");
console.log(name);
}}>显示全称</button>
</div>;
}
useState注意项
-1. useState的set方法是异步方法,需要组件内部更新之后state才是更新后的值
-2. setState方法不具有类组件的setState合并多个state的作用,在更新时,其他值一同更新
function App() {
const [data,setData] = useState({name: 'test', age: 10});
return <div>
<p>{data.name}</p>
<button onClick={()=>{
setName({...data, name: "hello"});
}}>显示全称</button>
</div>;
}
-3. 同一个组件内可以使用多个useState
useEffect
Effect翻译成专业术语称之为副作用。什么是副作用呢?网络请求、DOM操作都是副作用的一种,useEffect就是专门用来处理副作用的。在类组件中副作用通常在componentDid-Mount和componentDidUpdate中进行处理,而 useEffect就相当于componentDidMount、com-ponentDidUpdate和componentWillUnmount的集合体。useEffect包括两个参数执行时的回调函数和依赖参数,并且回调函数还有一个返回函数
import React, { useState, useEffect } from 'react';
function Course(){
const [course,setCourse] = useState("English");
const [num,setNum] = useState(1);
useEffect(()=>{
console.log("组件挂在或更新");
return ()=>{
console.log("清理更新前的一些全局内容,或检测组件即将卸载");
}
},[num]);
console.log(1);
return <div>
<div>
选择课程:
<select
value = {course}
onChange = {({target})=>{
setCourse(target.value);
}}
>
<option value="Math">Math</option>
<option value="English">English</option>
</select>
</div>
<div>
购买数量:<input
type="number"
value={num}
min={1}
onChange={({target})=>{
setNum(target.value);
}}
/>
</div>
</div>
}
function App() {
const [show,setShow] = useState(true)
return <div>
{show?<Course />:""}
<button onClick={()=>{
setShow(!show);
}}>{show?"隐藏课程":"显示课程"}</button>
</div>;
}
依赖参数,其本身是一个数组,在数组中放入要依赖的数据,当这些数据有更新时,就会执行回调函数。整个组件的生命周期过程如下:
组件挂载→执行副作用(回调函数)→组件更新→执行清理函数(返还函数)→执行副作用(回调函数)→组件准备卸载→执行清理函数(返还函数)→组件卸载。
上文讲过 useEffect是componentDidMount、componentDidUpdate 和componentWillUnmount
的集合体,如果单纯只想要在挂载后、更新后、卸载前其中之一的阶段执行,可以参考以下操作。
①componentDidMount。如果只想要在挂载后执行,可以把依赖参数置为空,这样在更新时就不会执行该副作用了。
②componentWilUnmount。如果只想要在卸载前执行,同样把依赖参数置为空,该副作用的返还函数就会在卸载前执行。
③componentDidUpdate。只检测更新相对比较麻烦,需要区分更新还是挂载需要检测依赖数据和初始值是否一致,如果当前的数据和初始数据保持一致就说明是挂载阶段,当然安全起见应和上一次的值进行对比,若当前的依赖数据和上一次的依赖数据完全一样,则说明组件没有更新。这种情况需要借助useRef的原因在于ref如果和数据绑定的话,数据更新时ref并不会自动更新,这样就可以获取到更新前数据的值。在下例中,可以看到useEffect模拟的各个阶段,当然也可以看到一个组件中可以拥有多个 useEffect。
function Course(){
const [course,setCourse] = useState("English");
const [num,setNum] = useState(1);
let prevCourse = useRef(course);
let prevNum = useRef(num);
useEffect(()=>{
console.log("组件挂在阶段");
return ()=>{
console.log("组件卸载之前");
}
},[]);//一定注意依赖参数传入一个空数组,不传的话组件的任何更新都会调用该副作用
useEffect(()=>{
if(course != prevCourse.current
|| num != prevNum.current){
// 如果当前值和上一次值不一样则代表组件又更新
console.log("组件更新");
// 这里注意 ref 不会自动更新,需要我们手动进行更新
prevCourse.current = course;
prevNum.current = num;
}
},[course,num]);
return <div>
<div>
选择课程:
<select
value = {course}
onChange = {({target})=>{
setCourse(target.value);
}}
>
<option value="English">English</option>
<option value="Math">Math</option>
</select>
</div>
<div>
购买数量:<input
type="number"
value={num}
min={1}
onChange={({target})=>{
setNum(target.value);
}}
/>
</div>
</div>
}
function App() {
const [show,setShow] = useState(true)
return <div>
{show?<Course />:""}
<button onClick={()=>{
setShow(!show);
}}>{show?"隐藏课程":"显示课程"}</button>
</div>;
}
useRef
useRef可以看成是 createRef 的Hook 版。使用时先把 ref存入变量,然后变量和DOM节点绑定,使用时通过ref的current属性来获取DOM节点
function App() {
let elP = useRef();
return <div>
<p ref={elP}>ref的实例值</p>
<button onClick={()=>{
console.log(elP.current);
}}>获取ref</button>
</div>;
}
useReducer
通常使用 useState和 useEffect基本能满足函数组件状态管理的需求,但也难免会有更加复杂的场景。我们现在虚拟一个计算器组件,需要实现计算器的加减乘除等操作,显然这些方法如果都分散定义,会导致状态管理混乱。这时我们想到了Redux状态管理的思想——单向数据流。
useReducer接受三个参数,第一个是处理状态更新的reducer,第二个参数是状态的初始值,第三个是状态初始函数
const [state, dispatch] = useReducer(reducer, initialArg, init);
状态更新函数
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
定义初始化状态值
const initialArg = {count: 0};
一般情况下,定义了这两个参数后就可以使用useReducer了
function Calculator() {
const [state, dispatch] = useReducer (reducer, initialArg);
return (
<View>
<Text>(state.count)</Text>
<View onClick={() => dispatch({type:'increment'})}>+</View>
<View onClick={() => dispatch ({type: 'decrement'})}>-</View>
</View>
)
}
useCallback
在函数组件中,定义函数很常见,但因为函数组件会随着状态值的更新而重新渲染,函数中定义的函数会频繁定义,这是非常影响性能的。Hooks提供了useCallback方法用于记忆函数。
useCallback接收两个参数,第一个参数是一个函数,第二个参数是一个数组,用于指定被记忆函数更新所依赖的值。使用示例如下:
const memoizedCallback = useCallback(() => {
doSomething (a, b);
},[a,b],)
第一个参数为需要记忆的函数,如事件处理回调函数。第二个参数如果指定为空数组,则被记忆的函数在组件初始化创建后永远保持不变直至组件卸载。回调函数使用 useCallback优化后是这样的:
function Demo() {
const handleClick = useCallback(() =>{console.log('clicked')}, [])
return (
<View onClick={handleClick}>view</View>
)
}
请注意,如果记忆函数中使用了组件的状态或者props值,则useCallback第二个参数需要指明对应的依赖,例如:
function Demo({id}) {
const [count ,setCount] = useState (0);
const handleClick = useCallback(() => {
console.log('clicked')
console.log(id, count)
setCount(count + 1)
//或者使用回调函数形式
//setCount(c => c + 1)
}, [id, count])
return (
<View onClick=(handleClick)>setCount</View>
)
}
useMemo
useCallback 用于记忆函数,useMemo用于记忆值,并且通常这个值是经过比较消
生能的计算得到的。useMemo接收两个参数:第一个参数用于处理耗时计算并返回需要记录的值;第二个参数为数组,用于指定被记忆函数更新所依赖的值
function Demo({list}) {
const memoList = useMemo(() => {
//doSomeThing表示性能计算函数
return doSomeThing (list)
}, [list]);
return (
<View>{memoList}</View>
)
}
自定义hook
可以自定义Hooks,可将组件中可复用逻辑提取出来,自定义的Hooks是个函数, 名称以“use”开头,函数内部可以调用其他Hooks,下面简单写个返回滚动条位置的hook
function useScrollY(){
let [scrollY,setScrollY] = useState(0);
function scroll(){
setScrollY(window.scrollY);
}
useEffect(()=>{
window.addEventListener("scroll",scroll);
return ()=>{
window.removeEventListener("scroll",scroll);
}
},[]);
return scrollY;
}
function App() {
let scrollY = useScrollY();
return <div style={{
border: "1px solid #000",
height: "1500px"
}}>
<p style={{
position: "fixed",
left: 0,
top: 0
}}>当前滚动条位置是:{scrollY}</p>
</div>;
}