什么是React-Hooks
你知道React-Hooks吗,说一下什么是React-Hooks?
- React-Hooks是一种函数组件,可以在函数中使用state以及React的一些其他特性;
- Hooks是React 16.8之后新增的React特性,内置了很多useState、useContext、useRef、useEffect等多种特性;当然,也可以根据自己的需求,自定义Hooks,要以use开头,并且在函数中,包含原有的hooks函数就可以了;
Hooks解决的问题
- 类组件的不足
- 类组件生命周期状态复杂,不利用复用
- 对于一个逻辑在不同的生命周期中都有,导致代码分散,不易于维护
- this指向问题
- Hooks的优势
- 去除了类组件的生命周期概念,更利于复用
- 副作用关注点分离,可能更专注于逻辑
Hooks的使用方法
useState使用方法
- 为什么使用useState?
- useState对应类组件中的setState,是为使用state使用的方法
- 具体使用方法
// 这里可以任意命名,因为返回的是数组,数组解构 const [state, setState] = useState(initialState); - 注意事项
- useState多次调用都是相互独立的
- useState会返回一个数组,一个是state值,一个是setState的操作函数,通过函数解构的方式获取,setState函数不会把新的 state 和旧的 state 进行合并,而是直接替换
- useState的初始值只会执行一次
useReducer使用方法
-
为什么使用useReducer?
- useReducer 和 redux 中 reducer 很像
- useState 内部就是靠 useReducer 来实现的
- useState 的替代方案,它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
- 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等
-
使用方法
let initialState = 0; // 如果你希望初始状态是一个{number:0} // 可以在第三个参数中传递一个这样的函数 ()=>({number:initialState}) // 这个函数是一个惰性初始化函数,可以用来进行复杂的计算,然后返回最终的 initialState const [state, dispatch] = useReducer(reducer, initialState, init);- 举个例子
const initialState = 0; function reducer(state, action) { switch (action.type) { case 'increment': return {number: state.number + 1}; case 'decrement': return {number: state.number - 1}; default: throw new Error(); } } function init(initialState){ return {number:initialState}; } function Counter(){ const [state, dispatch] = useReducer(reducer, initialState,init); return ( <> Count: {state.number} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ) } -
注意事项
- 暂无
useContext使用方法
-
为什么使用useContext?
- 跟class类组件中的context相对应,useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>
-
使用方法
import React, {useContext, useReducer} from 'react' const reducer = (state = 0, {type}) => { switch (type) { case "add": return state + 1 case 'delete': return state - 1 default: return state; } } const Context = React.createContext(null); const Child = () => { const [count, dispatch] = useContext(Context) return ( <div> <div>child...{count}</div> <button onClick={() => dispatch({type: 'add'})}>child add</button> <button onClick={() => dispatch({type: 'delete'})}>child delete</button> </div> ) } const Hook = () => { const [count, dispatch] = useReducer(reducer, 10) return ( <Context.Provider value={[count, dispatch]}> <div> <div>mom ... {count}</div> <Child/> <button onClick={() => dispatch({type: 'add'})}>mom add</button> <button onClick={() => dispatch({type: 'delete'})}>mom delete</button> </div> </Context.Provider> ) } export default Hook -
注意事项
- useContext接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
- 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定 当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值
- useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context
useEffect使用方法
- 为什么使用useEffect?
- useEffect对应的是class组件中的部分生命周期,分别为componentDidMount、componentDidUpdate、componentWillUnmount;
- 使用方法
- 比如在componentDidMount中,发送异步请求,模拟执行一次,第二个参数设置空数组即可
useEffect(()=>{ const users = 获取全国人民的信息() },[])- 每次更新的时候,都会执行的副作用函数,比如componentDidUpdate
useEffect(()=>{ const users = 每次都获取全国人民的信息() })- 可以设置在某些条件下才执行,比如,当text参数变化的时候
useEffect(()=>{ const users = 每次都获取全国人民的信息() }, [text])- 当useEffect的回调函数需要进行消除副作用时,即componentWillUnmount,可以直接返回消除副作用函数
useEffect(() => { const subscription = 订阅全国人民吃饭的情报! return () => { 取消订阅全国人民吃饭的情报! } },[]) - 注意事项
- effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。
- 副作用操作可以分两类:需要清除的和不需要清除的。
- useEffect 接收一个函数,该函数会在组件渲染到屏幕之后才执行,该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容
useRef使用方法
- 为什么使用useRef?
- useRef相当用class组件中的React.createRef
- 使用方法
- 相当于全局变量,一处被修改,其他地方都更新
const [count, setCount] = useState(0) const countRef = useRef(0) useEffect(() => { console.log('use effect...',count) const timer = setInterval(() => { console.log('timer...count:', countRef.current) setCount(++countRef.current) }, 1000) return ()=> clearInterval(timer) },[])- 普遍操作,用来更新DOM
const btnRef = useRef(null) - 注意事项
自定义Hooks组件
- 一定要以use开头
经常使用的优化方式
优化的本质就是减少render的次数,如何减少组件的render的次数?
当数据没有变化的时候,即使父组件更新,也应该避免子组件的更新
-
Object.is 浅比较
- Hook 内部使用 Object.is 来比较新/旧 state 是否相等
-
优化方法
- 使用 React.memo ,将函数组件传递给 memo 之后,就会返回一个新的组件,新组件的功能:如果接受到的属性不变,则不重新渲染函数;
- useCallback:接收一个内联回调函数参数和一个依赖项数组(子组件依赖父组件的状态,即子组件会使用到父组件的值) ,useCallback 会返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
- useMemo:把创建函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
-
举个🌰
import React,{useState,memo,useMemo,useCallback} from 'react';
function SubCounter({onClick,data}){
console.log('SubCounter render');
return (
<button onClick={onClick}>{data.number}</button>
)
}
SubCounter = memo(SubCounter);
let oldData,oldAddClick;
export default function Counter2(){
console.log('Counter render');
const [name,setName]= useState('计数器');
const [number,setNumber] = useState(0);
// 父组件更新时,这里的变量和函数每次都会重新创建,那么子组件接受到的属性每次都会认为是新的
// 所以子组件也会随之更新,这时候可以用到 useMemo
// 有没有后面的依赖项数组很重要,否则还是会重新渲染
// 如果后面的依赖项数组没有值的话,即使父组件的 number 值改变了,子组件也不会去更新
//const data = useMemo(()=>({number}),[]);
const data = useMemo(()=>({number}),[number]);
console.log('data===oldData ',data===oldData);
oldData = data;
// 有没有后面的依赖项数组很重要,否则还是会重新渲染
const addClick = useCallback(()=>{
setNumber(number+1);
},[number]);
console.log('addClick===oldAddClick ',addClick===oldAddClick);
oldAddClick=addClick;
return (
<>
<input type="text" value={name} onChange={(e)=>setName(e.target.value)}/>
<SubCounter data={data} onClick={addClick}/>
</>
)
}