【React Hooks】useState 用法
回顾 React
单向数据流
和angular双向绑定不同,React采用自上而下单向数据流的方式,管理自身的数据与状态。
在单向数据流中,数据只能由父组件触发,向下传递到子组件。
我们可以在父组件中定义state,并通过props的方式传递到子组件。
如果子组件想要修改父组件传递而来的状态,则只能给父组件发送消息,由父组件改变,再重新传递给子组件。
在React中,state与props的改变,都会引发组件重新渲染。
如果是父组件的变化,则父组件下所有子组件都会重新渲染。
在class组件中,组件重新渲染,是执行render方法。
而在函数式组件中,是整个函数重新执行
函数式组件
函数式组件与普通的函数几乎完全一样。
只不过函数执行完毕时,返回的是一个JSX结构。
function Hello() {
return <div>hello world.</div>
}
函数式组件非常简单,也正因如此,一些特性常常被忽略,而这些特性,是掌握React Hooks的关键。
- 函数式组件接收props作为自己的参数
import React from 'react';
interface Props {
name: string,
age: number
}
function Demo({ name, age }: Props) {
return [
<div>name: {name}</div>,
<div>age: {age}</div>
]
}
export default Demo;
-
props的每次变动,组件都会重新渲染一次,函数重新执行
-
没有this。那么也就意味着,之前在class中由于this带来的困扰就自然消失了
useState 使用
- useState Hook 让函数组件也可以有 state 状态,并进行读写操作
- 语法:
const [state, setState] = React.useState(initalState)
// x代表可以自定义,X代表首字母大写
const [xxx, setXxx] = React.useState(initalValue)
-
useState()
说明: -
参数:第一次初始化指定的值在内部作缓存
-
返回值:包含两个元素的数组,第1个为内部当前状态值,第2个为更新状态值的函数
-
setState()
的2种写法: -
setState(newValue)
:参数为非函数值,直接指定新的状态值,内部用其覆盖原来的值 -
setState(value=>newValue)
:参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值。
setCount(count+1)
setCount(count => {return count+1})
- useState() 不帮助你处理状态,
- 相较于 setState() 非覆盖式更新状态,
- useState() 覆盖式更新状态,
- 需要开发者自己处理逻辑。
- 用来举例的有内部状态的函数式组件
import React, { useState } from 'react';
export default function Counter() {
const [counter, setCounter] = useState(0);
// 利用useState声明状态,每当点击时,setCounter执行,counter递增
return (
<div>
<div key="a">{counter}</div>,
// 当用户点击按钮后,我们传递一个新的值给 setCount。
// React 会重新渲染 Counter 组件,并把最新的 count 传给它。
<button key="b" onClick={() => setCounter(counter + 1)}>
点击+1
</button>
</div>
)
}
- 如果counter是一个引用类型
// counter默认值为 { a: 1, b: 2 }
const [counter, setCounter] = useState({ a: 1, b: 2 });
// 此时counter的值被改为了 { b: 4 }, 而不是 { a: 1, b: 4 }
setCounter({ b: 4 });
// 如果想要得到 { a: 1, b: 4 }的结果,就必须这样
setCounter({ ...counter, b: 4 });
- 用下面的例子修改状态,会让组件重新渲染吗?
const [counter, setCounter] = useState({ a: 1, b: 2 });
// 修改counter的值
counter.b = 4;
setCounter(counter);
- useState接收一个值作为当前定义的state的初始值。
- 并且初始操作只有组件首次渲染才会执行。
// 首次执行,counter初始值为10
// 再次执行,因为在后面因为某种操作改变了counter,则获取到的便不再是初始值,而是闭包中的缓存值
const [counter, setCounter] = useState(10);
setCounter(20);
- 如果初始值需要通过较为复杂的计算得出,则可以传入一个函数作为参数,函数返回值为初始值。
- 该函数也只会在组件首次渲染时执行一次。
const a = 10;
const b = 20
// 初始值为a、b计算之和
const [counter, setCounter] = useState(() => {
return a + b;
})
state 两者区别
function state 和 class state
- 使用类组件
import React, { Component } from 'react';
//计数器
class App extends Component {
constructor(props) {
super(props);
//需要使用 state保存组组件中需更新的数据
this.state = { count: 0 }
}
changeCount = () => {
//更新树需要调用 setState
this.setState({
count: this.state.count + 1
})
}
render() {
return (<div>
<h2>计数器</h2>
<button onClick={this.changeCount}>++</button>
{this.state.count}
</div>);
}
}
export default App;
- 在函数式组件中使用
useState
//useState 使用状态值,第一个 hook
//1.引入useState 钩子
import React, { useState } from 'react';
//计数器
export function AppNumber() {
//2.设置state的初始值
//useState函数的参数 可以设置state状态的初始值
/*
useState函数的返回值一个数组,数组里有两个元素,
第一个元素 是 state的key
第二个元素是 可以更改 state状态 的函数名 相当于 setState
*/
//注意:修改state的状态值的函数名 起名时 需要 使用 set开头
const [count, setCount] = useState(5);
const changeIncrementCount = () => {
//3. 更改state的值
setCount(count + 1);
}
return (
<div>
<h2>计数器</h2>
count:{count}
<button onClick={changeIncrementCount}>+</button>
</div>
)
}
//计数器-对象形式的state
export function AppObject() {
// 定义对象形式的state
const [obj, setObj] = useState({
count: 10,
name: "lili"
})
const changeCount = () => {
setObj({ ...obj, count: obj.count + 1 })
}
return (
<div>
<h2>计数器</h2>
count:{obj.count}
<button onClick={changeCount}>+</button>
</div>
)
}
快照(闭包) vs 最新值(引用)
function state 保存的是快照,class state 保存的是最新值
- 在这个例子中,3秒内连续点击5次,页面上的数字会从0增长到5。
import React, { Component } from 'react';
//计数器
class App extends Component {
state = {
count: 0
}
changeCount = () => {
setTimeout(()=>{
this.setState({
count: this.state.count + 1
})
},3000)
}
render() {
return (<div>
<h2>计数器</h2>
<button onClick={this.changeCount}>++</button>
{this.state.count}
</div>);
}
}
export default App;
- 在这个例子中,3秒内连续点击5次,页面上的数字会从0增长到1。
//useState 使用状态值,第一个 hook
//1.引入useState 钩子
import React, { useState } from 'react';
//计数器
function App() {
const [count, setCount] = useState(0);
const changeIncrementCount = () => {
setTimeout(()=>{
setCount(count + 1);
},3000)
}
return (
<div>
<h2>计数器</h2>
count:{count}
<button onClick={changeIncrementCount}>+</button>
</div>
)
}
export default App;
分析:这主要是引用和闭包的区别
class 组件里面可以通过 this.state 引用到 count,
所以每次 setTimeout 的时候都能通过引用拿到上一次的最新 count,所以点击多少次最后就加了多少。
function 组件里面每次更新都是重新执行当前函数,
也就是说 setTimeout 里面读取到的 count 是通过闭包获取的,
而这个 count 实际上只是初始值,并不是上次执行完成后的最新值,所以最后只加了1次。
扩展:想要解决这个问题,那就涉及到另一个新的 Hook 方法 —— useRef。