useState
useState
返回的第一个值将始终是更新后最新的 state, 第二个值是更新state的函数
useState 的使用
const App = () => {
const [num, setNum] = useState(0)
const handleClick = () => {
setNum(num + 1)
}
return (
<div>
App: {num}
<p onClick={handleClick}>App Click:</p>
</div>
)
}
export default App
useEffect
完成副作用操作,组件渲染在屏幕后执行
const Effect1 = () => {
const [num, setNum] = useState(0)
useEffect(() => {
// 以后render都会执行
console.log('Effect1')
})
// useEffect模拟 componentDidMount
useEffect(() => {
console.log('Effect2')
}, [])
// useEffect 模拟 componentDidUpdate
useEffect(() => {
if(num !== 0) {
console.log('Effect2')
}
}, [num])
useEffect(() => {
console.log('Effect3')
// useEffect 模拟 componentWillUnmount
return () => {
console.log('componentWillUnmount')
}
}, [])
const handleClick = () => {
setNum(num + 1)
}
return (
<div>
Effect1
<p onClick={handleClick}>Effect1 Click:</p>
</div>
)
}
export default Effect1
补充:
补充一: useEffect 在刷新的的时候都会触发2次
补充二: useEffect 正确的为 DOM 设置事件监听,不应在函数中执行阻塞浏览器更新屏幕的操作
useContext
接收一个 context 对象,并返回该 context 的当前值
// context.js
import { createContext } from 'react'
let obj = {name: 'kris'}
const Context = createContext();
// app.js
// 引入obj, Context
const app = () => {
<Context.Provider value={obj}>
<Toolbar />
</Context.Provider>
}
// Toolbar.jsx
// 引入Context
const Toolbar = () => {
const { obj } = useContext(Context)
return(
<div>{obj.name}</div>
)
}
useReducer
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,和初始化的参数initialState, 并返回当前的 state 以及与其配套的 dispatch 方法
const initialState = {num: 1}
const reducer = (state, action) => {
switch(action.type) {
case: 'add'
return {num: state.num + 1}
case: 'reduce'
return {num: state.num - 1}
}
}
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return(
<p>{state.num}</p>
<button onClick={ ()=> dispatch({type: 'add'})}>+</button>
<button onClick={ ()=> dispatch({type: 'reduce'})}>-</button>
)
}
useContext 和 useReducer 来实现redux
// context.js
import { useReducer, createContext } from 'react'
export const Context = createContext();
const initialState = {num: 1}
const reducer = (state, action) => {
switch(action.type) {
case: 'add'
return {num: state.num + 1}
case: 'reduce'
return {num: state.num - 1}
}
}
export const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initState)
return (
<Context.Provider value={{ state, dispatch }}>{children}</Context.Provider>
)
}
// app.js
// 引入 context Provider
const App = () => {
<Context>
<Toolbar />
</Context>
}
//Toolbar.jsx
import { useContext } from 'react'
// 引入context.js 的 Context
const Toolbar = () => {
const { state, dispatch } = useContext(Context)
return(
<div>{state.num}</div>
<button onClick={ ()=> dispatch({type: 'add'})}>+</button>
<button onClick={ ()=> dispatch({type: 'reduce'})}>-</button>
)
}
补充React.memo
想直接写useCallback,useMemo,发现都有点绕不开memo,于是单独列出来
React.memo为高阶组件,和React.purecomponent 相似
React.memo: 第一个参数是自定义组件,第二个参数是一个函数,用来判断组件需不需要重新渲染。如果省略第二个参数,默认会对该组件的props进行浅比较
// parent.jsx
import { useState } from 'react'
import Child from './memoChild'
const MemoParent = () => {
const [num, setNum] = useState(0)
console.log('MemoParent')
return(
<div>
{num}
<button onClick={() => setNum(num + 1)}>num++</button>
<Child />
</div>
)
}
export default MemoParent
// child.jsx
import { memo } from 'react'
const Child = () => {
console.log('memo child')
const memoClick = () => {}
return (
<button onClick={memoClick}>memoClick</button>
)
}
export default Child
以上每次点击父元素的事件,子组件child, 都会刷新,造成子组件不必要的渲染
这时我们可以用react.memo 来进行优化,对child进行改造
import { memo } from 'react'
const Child = memo(() => {
console.log('memo child')
const memoClick = () => {}
return (
<button onClick={memoClick}>memoClick</button>
)
})
export default Child
再次点击父组件点击事件的时候,子组件不会刷新了
useCallback
第一次渲染时执行,缓存函数,之后只有在依赖项改变时才会更新缓存
useCallback
// parent.jsx
import { useState } from 'react'
import Child from './memoChild'
const Parent = () => {
const [num, setNum] = useState(0)
console.log('MemoParent')
const childClick = () => {
console.log('childClick')
}
return(
<div>
{num}
<button onClick={() => setNum(num + 1)}>num++</button>
<Child childClick = {childClick} />
</div>
)
}
export default Parent
import { memo } from 'react'
const Child = memo((props) => {
const { childClick } = props
console.log('memo child')
return (
<button onClick={childClick}>memoClick</button>
)
})
export default Child
当在child组件传入一个函数时,发现,再次点击父组件的num++,子组件会重新刷新
这时候useCallback的作用就来了,对父组件稍微的修改一下
import { useState, useCallback } from 'react'
import Child from './memoChild'
const MemoParent = () => {
const [num, setNum] = useState(0)
const [title, setTitle] = useState('')
console.log('MemoParent')
// 修改处
const childClick = useCallback(() => {
setTitle('xxxxxxxxx')
}, [])
return(
<div>
{num}
{title}
<button onClick={() => setNum(num + 1)}>num++</button>
<Child childClick = {childClick} />
</div>
)
}
export default MemoParent
结果: 再次点击 num++ 的时候,子组件不会render
结论: memo会进行一个浅比较,刚进行setXXX 的时候,会把props的参数进行一个浅比较,但是以上在进行setXXX 的时候,传入在子组件的函数地址已经变成另外一个,所以在进行比较的时候,他们是不相等的。 栈和堆
useMemo
第一次渲染时执行,缓存变量,之后只有在依赖项改变时才会重新计算记忆值
// parent.jsx
import { useState, useCallback } from 'react'
import Child from './memoChild'
const MemoParent = () => {
const [num, setNum] = useState(0)
const [title, setTitle] = useState('')
console.log('MemoParent')
const childClick = useCallback(() => {
setTitle('xxxxxxxxx')
}, [])
return(
<div>
{num}
<button onClick={() => setNum(num + 1)}>num++</button>
<Child title={title} childClick = {childClick} />
</div>
)
}
export default MemoParent
// child.jsx
import { memo } from 'react'
const Child = memo((props) => {
const { childClick, title } = props
console.log('memo child')
return (
<>
{title}
<button onClick={childClick}>memoClick</button>
</>
)
})
export default Child
其实如果熟悉了useCallback, 在来看useMemo,发现他们只是缓存的值不一样。
// parent.jsx
import { useState, useCallback, useMome } from 'react'
import Child from './memoChild'
const MemoParent = () => {
const [num, setNum] = useState(0)
const [title, setTitle] = useState('')
console.log('MemoParent')
const childClick = useCallback(() => {
setTitle('xxxxxxxxx')
}, [])
const memoTtle = useMome(() => ({ value: title }), [title])
return(
<div>
{num}
{title}
<button onClick={() => setNum(num + 1)}>num++</button>
<Child title={memoTtle} childClick = {childClick} />
</div>
)
}
export default MemoParent
// child.jsx
import { memo } from 'react'
const Child = memo((props) => {
const { childClick, title } = props
console.log('memo child')
return (
<>
{title.value}
<button onClick={childClick}>memoClick</button>
</>
)
})
export default Child
点击父组件的num++ 时候,不会刷新子组件
useRef useImperativeHandle forwardRef
useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref 对象在组件的整个生命周期内保持不变。
useImperativeHandle
可以让你在使用 ref
时自定义暴露给父组件的实例值
useImperativeHandle 应当与 forwardRef 一起使用
下面是3个实用的联合案列
// parent.jsx
import { useRef } from 'react'
import Child from './memoChild'
const Parent = () => {
const ref = useRef()
const getChildValue = () => {
ref.current.click()
ref.current.focus()
}
return(
<div>
<button onClick={getChildValue}>num++</button>
<Child ref={ref} />
</div>
)
}
export default Parent
// child.jsx
import { forwardRef, useImperativeHandle, useRef } from 'react'
const Child = (props, ref) => {
console.log('memo child')
const inputRef = useRef();
useImperativeHandle(ref, () => (
{
click: () => {
console.log('1111')
},
focus: () => {
inputRef.current.focus();
}
}
))
const childClick = () => {}
return (
<>
<input ref={inputRef} />
<button onClick={childClick}>memoClick</button>
</>
)
}
export default forwardRef(Child)