function App() {
const [count, setCount] = useState(0)
return (
<div>
{
count}
<button onClick={
() => setCount(count + 1)}>setCount</button>
</div>
)
}
export default App
接收一个状态初始值,返回一个数组,数组第一个元素是状态值,第二个元素是修改状态的方法
function useState(initialState) {
let state = initialState
function setState(newState) {
state = newState
}
return [state, setState]
}
function App() {
const [count, setCount] = useState(0)
return (
<div>
{
count}
<button onClick={
() => setCount(count + 1)}>setCount</button>
</div>
)
}
export default App
状态变更后重新渲染视图
import ReactDOM from 'react-dom'
function useState(initialState) {
let state = initialState
function setState(newState) {
console.log(newState)
state = newState
// 在状态更改完成后,重新渲染视图
render()
}
return [state, setState]
}
function render() {
ReactDOM.render(<App />, document.getElementById('root'))
}
function App() {
const [count, setCount] = useState(0)
return (
<div>
{
count}
<button onClick={
() => setCount(count + 1)}>setCount</button>
</div>
)
}
export default App
现在点击按钮数值变成1后,在此点击没有效果,打印结果 newState 一直是1。
这是因为 App 组件每次渲染都会重新执行 useState,重置 state 的值为 0。
为了避免组件每次渲染都重新初始化 state 的值,要将 state 存储在 useState 函数外部,先判断 state 是否有值,如果有则使用当前值,如果没有则设置为初始值。
let state
function useState(initialState) {
state = state ? state : initialState
function setState(newState) {
console.log(newState)
state = newState
// 在状态更改完成后,重新渲染视图
render()
}
return [state, setState]
}
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('张三')
return (
<div>
{
count}
<button onClick={
() => setCount(count + 1)}>setCount</button>
<br/>
{
name}
<button onClick={
() => setName('李四')}>setName</button>
</div>
)
}
当前显示的 count 和 name 的值都一样,因为一个 state 无法存储多个状态。
将 state 设置为一个数组,按照调用 useState 的顺序存储每次创建的状态值,这样就可以存储多个状态。
设置状态值的方法也同理。
import React from 'react'
import ReactDOM from 'react-dom'
let state = []
let setters = [] // 存储设置状态值的方法
let stateIndex = 0
function createSetter(index) {
return function(newState) {
if (typeof newState === 'function') {
// 如果传入的是回调函数
state[index] = newState(state[index])
} else {
state[index] = newState
}
// 在状态更改完成后,重新渲染视图
render()
}
}
function useState(initialState) {
state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState
if (!setters[stateIndex]) {
// 错误:直接保存一个 set 方法,方法中使用 stateIndex 会采用最终的值
// setters.push(function(newState) {
// state[stateIndex] = newState
// render()
// })
// 正确:声明一个函数,采用闭包的方式,保存各自状态的 index
setters.push(createSetter(stateIndex))
}
const value = state[stateIndex]
const setter = setters[stateIndex]
stateIndex++
return [value, setter]
}
function render() {
// 重置 index
stateIndex = 0
ReactDOM.render(<App />, document.getElementById('root'))
}
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('张三')
return (
<div>
{
count}
<button onClick={
() => setCount(count => count + 1)}>setCount</button>
<br/>
{
name}
<button onClick={
() => setName('李四')}>setName</button>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
当前模拟 useState 实现原理的组件直接挂载到 root 元素下,没有嵌套其它组件。若嵌套,state 和 setters 则会出现重复数据。
React 中的 useState 基于 useReducer 实现,返回的数组的第二个元素其实是 dispatch 方法。
useState
调用时会创建一个 hook 对象并挂载到节点 Fiber 对象中,其中包括:
- state:缓存的上一次 state 和当前 state
- queue:dispatch 触发队列,保证多个触发 dispatch 的任务
- next:hook 链,保证多次调用 useState 的定位
import ReactDOM from 'react-dom'
/* ---------- useState ---------- */
let state = []
let setters = [] // 存储设置状态值的方法
let stateIndex = 0
function createSetter(index) {
...}
function useState(initialState) {
...}
function render() {
// 重置 index
stateIndex = 0
ReactDOM.render(<App />, document.getElementById('root'))
}
/* ---------- useEffect ---------- */
// 上一次的依赖值
let prevDepsAry = []
function useEffect(callback, depsAry) {
// 判断 callback 是不是函数
if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect 函数的第一个参数必须是函数')
// 判断 depsAry 有没有被传递
if (typeof depsAry === 'undefined') {
// 没有传递
callback()
} else {
// 判断 depsAry 是不是数组
if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect 函数的第二个参数必须是数组')
// 将当前的依赖值和上一次的依赖值作对比 如果有变化 调用 callback
const hasChange = depsAry.every((dep, index) => dep !== prevDepsAry[index])
// 判断值是否有变化
if (hasChange) {
callback()
}
// 同步依赖值
prevDepsAry = depsAry
}
}
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('张三')
useEffect(() => {
console.log('Hello')
}, [count])
return (
<div>
{
count}
<button onClick={
() => setCount(count => count + 1)}>setCount</button>
<br/>
{
name}
<button onClick={
() => setName('李四')}>setName</button>
</div>
)
}
export default App
import ReactDOM from 'react-dom'
/* ---------- useState ---------- */
let state = []
let setters = [] // 存储设置状态值的方法
let stateIndex = 0
function createSetter(index) {
...}
function useState(initialState) {
...}
function render() {
// 重置 index
stateIndex = 0
effectIndex = 0
ReactDOM.render(<App />, document.getElementById('root'))
}
/* ---------- useEffect ---------- */
// 上一次的依赖值
let prevDepsAry = []
let effectIndex = 0
function useEffect(callback, depsAry) {
// 判断 callback 是不是函数
if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect 函数的第一个参数必须是函数')
// 判断 depsAry 有没有被传递
if (typeof depsAry === 'undefined') {
// 没有传递
callback()
} else {
// 判断 depsAry 是不是数组
if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect 函数的第二个参数必须是数组')
// 获取上一次的依赖值
const prevDeps = prevDepsAry[effectIndex]
// 判断上一次的依赖值是否存在 如果不存在 调用 callback
// 将当前的依赖值和上一次的依赖值作对比 如果有变化 调用 callback
const hasChange = prevDeps ? depsAry.every((dep, index) => dep !== prevDeps[index]) : true
// 判断值是否有变化
if (hasChange) {
callback()
}
// 同步依赖值
prevDepsAry[effectIndex] = depsAry
effectIndex++
}
}
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('张三')
useEffect(() => {
console.log('Hello')
}, [count])
useEffect(() => {
console.log('World')
}, [name])
return (
<div>
{
count}
<button onClick={
() => setCount(count => count + 1)}>setCount</button>
<br/>
{
name}
<button onClick={
() => setName('李四')}>setName</button>
</div>
)
}
export default App
import {
useState } from 'react'
/* ---------- useReducer ---------- */
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState)
function dispatch(action) {
const newState = reducer(state, action)
setState(newState)
}
return [state, dispatch]
}
function App() {
function reducer(state, action) {
switch (action.type) {
case 'increment':
return state + 1
case 'decrement':
return state - 1
default:
return state
}
}
const [count, dispatch] = useReducer(reducer, 0)
return (
<div>
{
count}
<button onClick={
() => dispatch({
type: 'increment'})}>+1</button>
<button onClick={
() => dispatch({
type: 'decrement'})}>-1</button>
</div>
)
}
export default App