什么是钩子(hooks)?
- 消息处理的一种方法,用来监视指定程序
- 函数组件中需要处理副作用,可以用钩子把外部代码”钩”进来
- Hooks的目的是为了给函数式组件加上状态
-
useState
const App: React.FC = (props) => {
const [count, setCount] = useState(0)
return (
<div className={styles.app}>
<button
onClick={() => {
setCount(count + 1)
}}
>
{count}
</button>
</div>
)
}
setCount是异步的,即使有多个setCount也只会执行一次
sideEffect
纯函数:
- 函数与外界的只有唯一一个渠道进行沟通,通过传入参数和返回值进行沟通。
- 相同的传入参数永远只会有相同的返回值。
副作用:
- 除了返回值,还做了其他事情,例如修改全局变量
useEffect
- 如果不传第二个参数,每次渲染都会调用useEffect, 相当于componentDidUpdate
- 第二个参数传[], 相当于componentDidMount
useEffect可以return一个清理函数,相当于componentWillUnmount时调用
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
在useEffect中使用async/await
直接将回调函数写成async/await是不行的,因为async默认返回的类型是Promise,与useEffect冲突
正确写法是先声明一个函数再调用useEffect(() => {
const getRobotGallery = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await response.json()
setRobotGallery(data)
}
getRobotGallery()
}, [])
处理loading
声明一个loading标记
const [loading, setLoading] = useState<boolean>(false)
useEffect(() => {
const fetchData = async () => {
setLoading(true)
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await response.json()
setRobotGallery(data)
setLoading(false)
}
fetchData()
}, [])
根据标记显示
{!loading ? (
<div className={styles.robotList}>
{robotGallery.map((r: any) => (
<Robot id={r.id} name={r.name} email={r.email} />
))}
</div>
) : (
<div>loading 数据加载中...</div>
)}
处理error
用try…catch
const [errorMsg, setErrorMsg] = useState('')
useEffect(() => {
const fetchData = async () => {
setLoading(true)
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
)
const data = await response.json()
setRobotGallery(data)
} catch (e: any) {
setErrorMsg(e.message)
}
setLoading(false)
}
fetchData()
}, [])
context和useContext
context可以避免数据通过props层层传递
在index.tsx中注册context ```jsx const defaultContextValue = { name: ‘jack’, } export const appContext = React.createContext(defaultContextValue) // 参数是默认值
ReactDOM.render(
在组件中消费context, comsumer必须使用函数形式传递value
```jsx
import { appContext } from '../index'
const Robot: React.FC<RobotProp> = ({ id, name, email }) => {
return (
<appContext.Consumer>
{(value) => (
<div className={styles.cardContainer}>
<img src={`https://robohash.org/${id}`} alt="robot" />
<h2>{name}</h2>
<p>{email}</p>
<p>作者:{value.name}</p>
</div>
)}
</appContext.Consumer>
)
}
用useContext代替consumer
import { appContext } from '../index'
import { useContext } from 'react'
const Robot: React.FC<RobotProp> = ({ id, name, email }) => {
const user = useContext(appContext)
return (
<div className={styles.cardContainer}>
<img src={`https://robohash.org/${id}`} alt="robot" />
<h2>{name}</h2>
<p>{email}</p>
<p>作者:{user.name}</p>
</div>
)
}
context是局部的全局变量
useContext可以和useReducer或者useState配合,将reducer或者state传递到任意组件
在class组件中只能使用context.consumer
封装context provider组件
import React, { useState } from 'react'
interface DefaultContextValue {
name: string
shoppingCartItems: { id: number; name: string }[]
}
const defaultContextValue: DefaultContextValue = {
name: 'jack',
shoppingCartItems: [],
}
interface StateContext {
state: DefaultContextValue
setState:
| React.Dispatch<React.SetStateAction<DefaultContextValue>>
| undefined
}
export const appContext = React.createContext<StateContext>({
state: defaultContextValue,
setState: undefined,
})
export const AppStateProvider: React.FC = (props) => {
const [state, setState] = useState(defaultContextValue)
return (
<appContext.Provider value={{ state, setState }}>
{props.children}
</appContext.Provider>
)
}
这里为了把state和setState作为一个对象传给一个context,用typescript才这样写
也可以把state和setState分别注册成context, 使用2个provider
import React, { useState } from 'react'
interface DefaultContextValue {
name: string
shoppingCartItems: { id: number; name: string }[]
}
const defaultContextValue: DefaultContextValue = {
name: 'jack',
shoppingCartItems: [],
}
export const appContext = React.createContext(defaultContextValue)
export const appSetStateContext = React.createContext<React.Dispatch<React.SetStateAction<DefaultContextValue>>
| undefined>(undefined)
export const AppStateProvider: React.FC = (props) => {
const [state, setState] = useState(defaultContextValue)
return (
<appContext.Provider value={state}>
<appSetStateContext.Provider value={setState}>
{props.children}
</appSetStateContext.Provider>
</appContext.Provider>
)
}
实现加入购入车功能
const Robot: React.FC<RobotProp> = ({ id, name, email }) => {
const store = useContext(appContext)
const addToCart = () => {
if (store.setState) {
store.setState({
...store.state,
shoppingCartItems: [...store.state.shoppingCartItems, { id, name }],
})
}
}
return (
<div className={styles.cardContainer}>
<img src={`https://robohash.org/${id}`} alt="robot" />
<h2>{name}</h2>
<p>{email}</p>
<p>作者:{store.state.name}</p>
<button onClick={addToCart}>加入购物车</button>
</div>
)
}
高阶组件Higher-Order Components(HOC)
高阶组件是参数为组件,返回值为新组件的函数。高阶组件都以with开头
如果两个组件有相似的代码逻辑,应使用高阶组件封装,提高代码复用率
文档
可以返回函数式组件或者类组件
import { appContext } from '../AppState'
import { useContext } from 'react'
import { RobotProp } from './Robot'
export const withAddToCart = (
ChildComponent: React.ComponentType<RobotProp>
) => {
// return class extends React.Component {}
return (props: any) => {
const store = useContext(appContext)
const addToCart = (id: number, name: string) => {
if (store.setState) {
store.setState({
...store.state,
shoppingCartItems: [...store.state.shoppingCartItems, { id, name }],
})
}
}
return <ChildComponent {...props} addToCart={addToCart} store={store} />
}
}
使用
import styles from './Robot.module.css'
import { StateContext } from '../AppState'
import { withAddToCart } from './AddToCart'
export interface RobotProp {
id: number
name: string
email: string
store: StateContext
addToCart: (id: number, name: string) => void
}
const Robot: React.FC<RobotProp> = ({ id, name, email, store, addToCart }) => {
return (
<div className={styles.cardContainer}>
<img src={`https://robohash.org/${id}`} alt="robot" />
<h2>{name}</h2>
<p>{email}</p>
<p>作者:{store.state.name}</p>
<button onClick={() => addToCart(id, name)}>加入购物车</button>
</div>
)
}
export default withAddToCart(Robot)
自定义hook
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
自定义hook可以代替HOC高阶组件
export const useAddToCart = () => {
const store = useContext(appContext)
const addToCart = (id: number, name: string) => {
if (store.setState) {
store.setState({
...store.state,
shoppingCartItems: [...store.state.shoppingCartItems, { id, name }],
})
}
}
return {
store,
addToCart,
}
}
使用
import styles from './Robot.module.css'
import { useAddToCart } from './AddToCart'
export interface RobotProp {
id: number
name: string
email: string
}
const Robot: React.FC<RobotProp> = ({ id, name, email }) => {
const { store, addToCart } = useAddToCart()
return (
<div className={styles.cardContainer}>
<img src={`https://robohash.org/${id}`} alt="robot" />
<h2>{name}</h2>
<p>{email}</p>
<p>作者:{store.state.name}</p>
<button onClick={() => addToCart(id, name)}>加入购物车</button>
</div>
)
}
export default Robot