Hook
React的Hook就是一些React提供的内置函数,这些函数可以让函数组件拥有和类组件一样的组件状态(state)以及模拟类组件的生命周期函数。但是不要什么业务都使用Hook,要在合适的时候使用Hook,否则会造成性能问题。(能不用的时候就不要用,当遇到性能不好优化的时候,自然会想到使用Hook)
所有的Hook只能在函数组件中使用,在自定义的事件函数以及类组件不能使用Hook
useState
useState和类组件的this.state一样,都是用来管理组件状态的。在React的Hook没出来之前,函数组件也叫做Functional Stateless Component(FSC:功能无状态的组件),这是因为函数组件本身的函数体就是render,每次重新渲染时就会重新运行整个函数并生成新的函数作用域。所以同一个函数组件是不能够共用状态的。 而类组件是重新运行render函数,组件状态并不会重新创建。
useState允许函数组件将自己的状态持久化并存储在React运行时的某个地方,这样在函数组件每次重新渲染的时候都可以从这个地方拿到该状态,而且当该状态被更新的时候,组件也会重渲染。
state的创建和更新
useState基本使用
// App.js
// 函数组件
import { useState } from 'react'
import Son from './son.js';
function App() {
let msg = useState("I Am Gloria");
console.log(msg);
return (
<>
<h1>App 组件</h1>
<Son></Son>
</>
)
}
export default App;
useState函数返回值是一个数组,数组中第一个元素是当前state的最新值,第二个元素是一个方法,该方法是用来更新state的函数,函数传入的值就是要修改的数据,而且调用该函数会刷新页面,并重新取值使用的state,将state修改为最新的数据。
// App.js
import { useState } from 'react'
import Son from './son.js';
function App() {
let msg = useState("I Am Gloria");
let change = () => {
msg[1]('I Am Gloria World Tour');
};
let look = function () {
console.log(msg);
}
return (
<>
<h1>App 组件</h1>
<h5>{msg[0]}</h5>
<button onClick={change}>change</button>
<button onClick={look}>look</button>
<Son></Son>
</>
)
}
export default App;
常用的是通过解构赋值来创建state,效果同理。
// 函数组件
import { useState } from 'react'
import Son from './son.js';
function App() {
let [msg, setMsg] = useState("I Am Gloria");
let change = () => {
setMsg('I Am Gloria World Tour');
};
let look = function () {
console.log(msg, 'msg');
console.log(setMsg, 'setMsg')
}
return (
<>
<h1>App 组件</h1>
<h5>{msg}</h5>
<button onClick={change}>change</button>
<button onClick={look}>look</button>
<Son></Son>
</>
)
}
export default App;
msg和setMsg这两个变量名是自定义的,msg可用于组件内部使用创建的state数据,setMsg函数用于修改state,当修改后,所有使用过msg的地方都会重新取值(调用render刷新页面)。 一个函数组件中可以用多个useState。
useState注意事项
useState可以传入函数
const [ state , dispatch ] = useState(initData)
state:作为渲染视图的数据源。dispatch:改变state的函数,可以理解为函数组件渲染的渲染函数initData有两种情况,第一种情况是非函数,将作为state初始化的值。第二种情况是函数,函数的返回值作为state初始化的值。这个函数只会被调用一次,用在初始值是动态值的情况。
比如:
// 函数组件
import { useState } from "react"
function App(props) {
const propsCount = props.count || 0;
let [count, setCount] = useState(propsCount)
return (
<>
<p>{count}</p>
<button onClick={() => { setCount(count++) }}>add</button>
</>
)
}
export default App;
const propsCount = props.count || 0 是不合理的,因为每次重新渲染函数组件时都会执行这行代码,而这行代码的意图是在初始化的时候执行,所以需要在useState的参数里放入一个函数。传入的函数仅在初始渲染时执行一次,以获得初始状态。
useState更新数据
在使用 useState 的 dispatch 更新 state 的时候,会浅比较两次 state ,发现 state 相同,不会开启更新任务。对于引用数据如果两次 state 指向了相同的引用地址,就会认为 state 相等,所以不会发生视图更新了。
// 函数组件
import { useState } from 'react'
function App() {
let [obj, setObj] = useState({ name: "G.E.M.", title: "I am gloria world tour" });
let change = () => {
obj.name='golden stars';
setObj(obj);
};
let look = function () {
console.log(obj);
}
return (
<>
<p>{obj.name}</p>
<p>{obj.title}</p>
<button onClick={change}>change</button>
<button onClick={look}>look</button>
</>
)
}
export default App;
当状态是一个引用数据时,可以使用展开运算符浅拷贝引用数据,重新申请一个内存空间,然后更新部分状态。
import { useState } from 'react'
function App() {
let [obj, setObj] = useState({ name: "G.E.M.", title: "I am gloria world tour" });
let change = () => {
obj.name='golden stars';
// {...obj}大括号在js语法中就是创建对象的直接量,此处就是传入了一个装有响应式对象的所有成员的新对象
setObj({...obj});
};
let look = function () {
console.log(obj);
}
return (
<>
<p>{obj.name}</p>
<p>{obj.title}</p>
<button onClick={change}>change</button>
<button onClick={look}>look</button>
</>
)
}
export default App;
useState返回数组中第二个元素用于修改状态的函数是全量替代。 全量替代就是传入的数据会把原来的数据全部替换。 而类组件的浅归并是传入的数据原来有就会更新,原来没有的数据就会创建该数据。
import { useState } from 'react'
function App() {
let [obj, setObj] = useState({ name: "G.E.M.", title: "I am gloria world tour" });
let change = () => {
obj = setObj({ msg: 'golden stars', name: "Gloria" });
};
let look = function () {
console.log(obj);
}
return (
<>
<p>{obj.name}</p>
<p>{obj.title}</p>
<button onClick={change}>change</button>
<button onClick={look}>look</button>
</>
)
}
export default App;
传入对象会把原来的覆盖,传入的对象中没有
title属性,取属性值就是undefined,undefined不会在页面渲染。
setState可以使用函数形式的:如果需要基于前一个状态来计算新的状态,可以使用函数形式的setState。
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
// 增加计数器
const increment = () => {
setCount(count + 1);
};
// 减少计数器
const decrement = () => {
setCount(count - 1);
};
// 重置计数器 const reset = () => {
setCount(0);
};
// 根据最新的值重新赋值
const setToLatestValue = () => {
// prevCount是前一个状态
setCount(prevCount => prevCount * 2);
};
return (
<div> <h1>当前计数: {count}</h1>
<button onClick={increment}>增加</button>
<button onClick={decrement}>减少</button>
<button onClick={reset}>重置</button> <button onClick={setToLatestValue}>设置为最新值的两倍</button> </div> );
};
export default Counter;
useState是异步操作
useState返回的修改状态的dispatch函数没有回调函数。无论是useState还是类组件的this.setState都是异步调用的,也就是说每次组件调用完之后都不能立即拿到最新的state值。
为了解决这个问题,类组件的this.setState中第一个参数是要修改的数据,第二个参数是一个回调函数,用来获取到最新的state值:
this.setState(newState, state => { console.log( state)})
// 类似于vue的watch监听,在组件更新后useEffect传入的回调函数会执行,此时拿到的数据就是修改之后最新的数据
useEffect(() => { console.log(state) }, [state]);
函数组件的setState函数不存在这么一个可以拿到最新state的回调函数,不过我们可以使用useEffect来实现相同的效果。