useState
基本使用:
// 这里可以任意命名,因为返回的是数组,数组解构
const [state, setState] = useState(initialState);
const [n,setN] = React.useState(0)
const [user,setUser] = React.useState({name:"张三"})
注意事项:
1. 不可局部更新
如果state是一个对象,是不可以合并属性的。
import React, { useState } from 'react'
function App() {
const [user, setUser] = useState({ name: "Wangpf", age: 18 })
const changeState = () => {
setUser({
name: "Mark"
})
}
return (
<div>
<h2>名字:{user.name}</h2>
<h2>年龄:{user.age}</h2>
<button onClick={changeState}>改变</button>
</div>
)
}
export default App
看代码所示,当我点击按钮时,它会把名字改为 Mark ,但是 age 会显示不见, 这就是因为它不会局部更新,不会帮我们合并属性,因此需要我们自己加上。
const changeState = () => {
setUser({
...user,
name: "Mark"
})
}
2. 地址会变
setState(obj),如果obj地址不变,那么React就认为数据没有变化。
意思就是,我们不能在原来的数据上进行操作,而是重新写一个新的对象来覆盖之前的。
useState 和 setState 都可以接收函数
// useState
const [user, setUser] = useState(() => {
return { name: "Wangpf", age: 18 }
})
// setState
const changeState = () => {
setUser((user) => {
return {
...user,
name: "Mark"
}
})
}
useEffect
- Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
- React中的副作用操作: 发ajax请求数据获取 设置订阅 / 启动定时器 手动更改真实DOM
- 语法和说明:
-
useEffect(() => { // 在此可以执行任何带副作用操作 return () => { // 在组件卸载前执行 // 在此做一些收尾工作, 比如清除定时器/取消订阅等 } }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
-
- 可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
useEffect(() => {
console.log('第一次渲染之后及之后每次都会执行');
})
useEffect(() => { // componentDidMount
console.log("只执行第一次渲染之后");
}, [])
useEffect(() => { // componentDidUpdate()
if (count !== 0) {
console.log('count变化了');
}
}, [count])
useEffect(() => {
return () => { componentWillUnmount()
}
}, [])
useContext
“上下文”
- 全局变量 是全局的“上下文”
- “上下文”是局部的全局变量
使用步骤:
- const XxxContext = React.createContext()
- 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
- <xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider>
- 后代组件读取数据
- const {数据} = useContext(xxxContext)
适合用于祖孙组件的通信
使用代码如下:
import React, { createContext, useContext, useState } from 'react'
const Context = createContext(null)
function App() {
const [count, setCount] = useState(0)
return (
<Context.Provider value={{ count, setCount }}>
<div>
<A />
</div>
</Context.Provider>
)
}
function A() {
return (
<div>
<h2>我是A组件</h2>
<B />
</div>
)
}
function B() {
const { count, setCount } = useContext(Context)
const increment = () => {
setCount(n => n + 1)
}
return (
<div>
<h3>我是B组件</h3>
<div>count:{count}</div>
<button onClick={increment}>+1</button>
</div>
)
}
export default App
useReducer
此hooks API 践行了Redux的思想
逻辑步骤:
- 创建初始值 initialState
- 创建所有操作 reducer = (state, action)=>{}
- 传给 useReducer,得到读写的API
- 调用并写 {{type:'操作类型'}}
详细代码如下:
// 初始化状态
const initialState = { count: 0 }
const reducer = (state, action) => {
const { type, data } = action
if (type === 'increment') {
return { count: state.count + data }
} else if (type === 'decrement') {
return { count: state.count - data }
} else {
throw new Error('unknown type')
}
}
function A() {
const [state, dispatch] = useReducer(reducer, initialState)
const { count } = state
const increment = () => {
dispatch({ type: "increment", data: 1 })
}
const decrement = () => {
dispatch({ type: "decrement", data: 2 })
}
return (
<div>
<h2>我是A组件</h2>
<h4>count:{count}</h4>
<button onClick={increment}>加</button>
<button onClick={decrement}>减</button>
</div>
)
}
useMemo
通过一个实例,来理解下 useMemo的用法。
import React, { useState } from 'react'
function App() {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const updateN = () => {
setN(n => n + 1)
}
const updateM = () => {
setM(m => m + 1)
}
return (
<div>
<button onClick={updateN}>update n {n}</button>
<button onClick={updateM}>update m {m}</button>
<A data={m} />
</div>
)
}
function A(props) {
console.log('A组件执行了');
return <div>A组件:{props.data}</div>
}
export default App
注意打印console.log的位置。
当我们点击APP组件的更新N的按钮时候,A组件会发生改变,但是M是没有改变的啊。
结论:不管我们是否改变了A组件的数据,我们会发现A组件会被重新渲染的。这就意味着性能的损耗。
因此,为了优化这个,React.memo 就派上用场了。 只需把A组件改为:
const A = React.memo((props) => {
console.log('A组件执行了');
return <div>A组件:{props.data}</div>
})
这样,只有APP组件的M数据不发生改变,那么A组件就不会重新渲染。
但是!!这样有个BUG,
当我给A组件绑定一个事件时,
import React, { useState, memo } from 'react'
function App() {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const updateN = () => {
setN(n => n + 1)
}
const updateM = () => {
setM(m => m + 1)
}
const changeA = () => {
}
return (
<div>
<button onClick={updateN}>update n {n}</button>
<button onClick={updateM}>update m {m}</button>
<A data={m} onClick={changeA} />
</div>
)
}
const A = memo((props) => {
console.log('A组件执行了');
return <div onClick={props.changeA}>A组件:{props.data}</div>
})
export default App
这样,即时我不该关于A组件的任何数据,A组件也会随着发生渲染,
问题的原因是因为:当我们重新渲染APP组件时,它会自动调用该changeA事件,那么它就会改变A组件的渲染。
解决该问题的方法就是: 使用 useMemo
const changeA = useMemo(() => {
},[])
总结
特点
- 第一个参数是 () => value
- 第二参数是 依赖 [xx]
- 只有当依赖变化时,会计算出新的value
- 如果依赖不变,那么就重用之前的value
是不是 Vue 中的 computed?
注意项:
- 如果 ()=> value 中的value 是个函数的话,那么你要写成:useMemo(()=>{(x)=>{xxxxxx}})
- 那么这就是一个返回函数的函数啊。 因此, useCallback 出来了
useCallback
下面俩个是等价的。
const changeA = useMemo(() => {
return m => console.log(m)
}, [])
const changeA = useCallback(m => console.log(m), [])
useRef
useRef返回的是一个可变的ref对象,其 xx.current 属性也就是 useRef(inital)中的inital初始化值。
所以注意 const xxxRef = useRef(0) 中, 此处的 0 实际是个对象为:{current:0}
为什么是对象的原因请看用处二
用处一: 获取 元素,可以做一些操作元素之类
通过如下简单实例可以明白
import React, { useRef } from 'react'
function App() {
const inputRef = useRef(null)
const onclick = () => {
console.log(inputRef.current); // 获得到input元素
}
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={onclick}>获取input</button>
</div>
)
}
export default App
用处二:保持可变变量
目的: 如果需要一个值,在组件不断render时保持不变
返回的ref对象在组件的整个生命周期内保持不变
举个常见的场景: 对定时器的清除操作。
我们需要确保 setInterval 的执行结果timer的引用,才能准确的清除对应的定时器。
import React, { useEffect, useRef } from 'react'
function App() {
const timerRef = useRef()
useEffect(() => {
timerRef.current = setInterval(() => {
console.log('timerRef');
}, 1000);
return () => {
timerRef.current && clearInterval(timerRef.current)
}
}, [])
return (
<div>
App....
</div>
)
}
export default App
forwardRef
import React, { useRef, forwardRef, useEffect } from 'react'
function App() {
const btnRef = useRef(null)
useEffect(() => {
console.log(btnRef.current);
}, [])
return (
<div>
<Button ref={btnRef} />
</div>
)
}
const Button = forwardRef((props, ref) => {
return (
<div>
<button ref={ref} {...props}>按钮</button>
</div>
)
})
export default App
React 会将 <Button ref={ref} />
元素的 ref 作为第二个参数传递给 React.forwardRef 函数中的渲染函数。
该渲染函数会将 ref 传递给 <button ref={ref} />
元素。
因此,当 React 附加了 ref 属性之后,inputRef.current 将直接指向 button
DOM 元素实例。
useImperativeHandle
如果想要给给该组件的属性改个名字,或者返回其他额外的属性或者方法,我们可以使用useImperativeHandle。
这个API具体我没去深入了解。
自定义 Hook
做一个hooks来实现渲染列表以及删除列表
组件:
// App组件
import React from 'react'
import useList from './hooks/useList'
function App() {
const { list, deleteIndex } = useList()
return (
<div>
<h2>小说列表</h2>
{
list ?
(<ul>
{list.map((item, index) => (
<li key={item.id}>{item.name}
<button onClick={() => { deleteIndex(index) }}>删除</button>
</li>
)
)}
</ul>) :
(<span>加载中....</span>)
}
</div>
)
}
export default App
自定义hook:
import { useState, useEffect } from 'react'
import {nanoid } from 'nanoid'
const useList = () => {
const [list, setList] = useState(null)
useEffect(() => {
ajax().then(list => {
setList(list)
})
}, [])
return {
list: list,
setList: setList,
deleteIndex: index => {
setList(list.slice(0,index).concat(list.slice(index+1)))
}
}
}
export default useList
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{id: nanoid(),name:"小王子"},
{id: nanoid(),name:"明朝那些事儿"},
{id: nanoid(),name:"三体"},
])
},2000)
})
}