React Hooks的作用是对函数型组件进⾏增强,让函数型组件可以存储状态, 可以拥有处理副作⽤的能⼒,可以在不使⽤类组件的情况下, 实现相同的功能。
useState方法接收参数,可以是普通值,也可以接收函数做为参数,如果是函数做为参数,组件初始化时会调用函数,返回的结果就时初始状态值;useState方法调用返回数组,数组参数为状态值跟改变状态值的方法;useState使用闭包的方式存储状态,能多次调用,因此需要外部声明数组变量存储状态值跟方法,并添加索引;当调用更改状态方法时重新渲染
let state = [];
let setters = [];
let stateIndex = 0;
function createSetter (index) {
return function (newState) {
state[index] = newState;
render ();
}
}
function useState (initialState) {
state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState;
setters.push(createSetter(stateIndex));
let value = state[stateIndex];
let setter = setters[stateIndex];
stateIndex++;
return [value, setter];
}
function render () {
stateIndex = 0;
effectIndex = 0;
ReactDOM.render(<App />, document.getElementById('root'));
}
useEffect是实现类组件当中生命周期函数的钩子。useEffect执行时机,传入空数组([]),组件初始化执行一次,同类组件componentDidMount;不传第二个参数时,组件初始化执行,组件更新也会执行,同类组件componentDidMount,componentDidUpdate;当useEffect传入的函数返回一个函数时,返回的函数执行时机会在组件卸载之后执行,同类组件的componentWillUnmount生命周期。当useEffect的第二个参数传入参数时,值发生变化,函数也会执行。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函数的第二个参数必须是数组');
// 获取上一次的状态值
let prevDeps = prevDepsAry[effectIndex];
// 将当前的依赖值和上一次的依赖值做对比 如果有变化 调用callback
let hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps[index]) === false : true;
// 判断值是否有变化
if (hasChanged) {
callback();
}
// 同步依赖值
prevDepsAry[effectIndex] = depsAry;
effectIndex++;
}
}
useReducer接收两个参数,第一个是reducer函数,第二个为初始状态值。useReducer调用返回数组,数组第一个值是状态值,第二个是dispatch方法。
代码实现:
function useReducer (reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch (action) {
const newState = reducer(state, action);
setState(newState);
}
return [state, dispatch];
}
上面是一些常用钩子的实现原理,下面是组件其他拓展钩子的使用方法
useContext在跨组件层级获取数据时简化获取数据的代码,实现跨组件间的通信;
通过creactContext方法调用返回的对象中的Provider包裹需要传值的组件,后辈组件通过useContext钩子调用creactContext方法返回的对象,获取到传递的值;
没有useContext之前需要通过creactContext方法返回对象的Consumer获取。
import { creactContext, useContext } from 'react'
const countContext = creactContext();
function App() {
return <countContext.Provider value={100}>
<Foo />
</countContext.Provider>
}
funtion Foo() {
const count = useContext(countContext)
return <div>{count}</div>
}
不使用useContext获取
import { creactContext, useContext } from 'react'
const countContext = creactContext();
function App() {
return <countContext.Provider value={100}>
<Foo />
</countContext.Provider>
}
funtion Foo() {
return <countContext.Consumer>
{
(count)=> <div>{count}</div>
}
</countContext.Consumer>
}
useMemo 的⾏为类似Vue中的计算属性, 可以监测某个值的变化, 根据变化值计算新值;useMemo 会缓存计算结果. 如果监测值没有发⽣变化, 即使组件重新渲染, 也不会重新计算. 此⾏为可以有助于避免在每个渲染上进⾏昂贵的计算
import { useMemo } from 'react'
const result = useMemo(() => {
// 如果count值发生变化函数重新执行
return result
}, [count])
memo跟useCallback都属于优化组件性能的方法,memo 类似类组件中的 PureComponent 和 shouldComponentUpdate,如果组件中的数据没有发⽣变化, 阻⽌组件更新;useCallback的作用缓存函数, 使组件重新渲染时得到相同的函数实例
下面案例通过useCallback缓存setCount方法,子组件在触发事件时,通过memo调用后就不会重新渲染;如果不执行useCallback直接将setCount传给子组件,子组件虽然调用了memo方法,但是会在重新渲染时认为传入的值发生了变化,所以会重新渲染,useCallback就完美规避的这个问题
import React, { useCallback, setCount } from 'react'
function Counter() {
const [count, setCount] = useState(0);
const resetCount = useCallback(() => setCount(0), [setCount]);
return <div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
<Test resetCount={resetCount} />
</div>
}
//Test
import { memo } from 'react'
function Test (props) => {
console.log('Test re-render');
return <div>
<button onClick={props.resetCount}>reset</button>
</div>
}
export default memo(Test)
useRef可以获取DOM元素对象,同时还可以保存数据,即使组件重新渲染, 保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染;useRef调用返回一个对象,存储的数据存放在对象的current属性中
import React, { useRef } from 'react'
function App() {
const username = useRef();
const handler = () => console.log(username); //{ current: input }
return <input ref={username} onChange={handler} />
}
⾃定义 Hook 是标准的封装和共享逻辑的⽅式;⾃定义 Hook 是⼀个函数, 其名称以 use 开头;⾃定义 Hook 其实就是逻辑和内置 Hook 的组合
function useUpdateInput (value) {
const [value, setValue] = useState(value)
return {
value,
onChange: event => setValue(event.target.value)
}
}
function App(){
const usernameInput = useUpdateInput('');
const passwordInput = useUpdateInput('');
const submitForm = event => {
event.preventDefault();
console.log(usernameInput.value);
console.log(passwordInput.value);
}
return <form onSubmit={submitForm}>
<input type="text" name="username" {...usernameInput }/>
<input type="password" name="password" {...usernameInput }/>
<input type="submit"/>
</form>
}