一、如何用Hooks替代组件的生命周期
react组件生命周期如图所示,分为render阶段、pre-commit阶段、commit阶段,这些常用的及不常的生命周期如何使用hooks来替代呢,下面一一为大家介绍:
- 使用hook替代componentDidMount方案,使用useEffect,第二个参数传入空数组
import React, { useState, useEffect } from 'react';
const columns = {...};
function Example() {
const [dataSource, setdataSource] = useState([]);
useEffect(() => {
const dataSource = await getSceneList();
setDataSource(dataSource);
}, []);
return <Table dataSource={dataSource} columns={columns} />
}
- 使用hook替代componentDidUpdate方案,使用useEffect,第二个参数更新依赖
import React, { useState, useEffect } from 'react';
const columns = {...};
function Example() {
const [query, setQuery] = useState({});
const [dataSource, setDataSource] = useState([]);
useEffect(() => {
const dataSource = await getSceneList();
setDataSource(setDataSource);
}, [query]);
return (
<div>
<Filter query={query} onChange={setQuery} />
<Table dataSource={dataSource} columns={columns} />
</div>);
}
- 使用hook替代componentWillUnmount方案,使用useEffect,返回函数会在组件卸载前执行
import React, { useState, useEffect } from 'react';
const columns = {...};
function Example() {
useEffect(() => {
const listener = e => {
console.log(e);
};
document.addEventListener('onClick', listener, false);
return () => {
document.removeEventListener('onClick', listener, false);
};
}, []);
return (<div></div>);
}
- 使用hook替代shouldComponentUpdate方案,使用React.memo
import React, { useState, useEffect } from 'react';
function Example(props) {
const {label, value} = props;
return (
<div>{label}:{value}</div>
);
}
export default React.memo(Example, (prevProps, nextProps) => {
return prevProps.label === nextProps.label
&& prevProps.value === nextProps.value;
});
- 使用hook替代getDerivedStateFromProps方案,记录preState进行比较
import React, { useState, useEffect } from 'react';
function ScrollView({row}) {
let [isScrollingDown, setIsScrollingDown] = useState(false);
let [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
- 使用hook替代getSnapshotBeforeUpdate方案,使用useLayoutEffect,执行时机差不多可以替代
import React, { useState, useEffect } from 'react';
function Example() {
useLayoutEffect(() => {
//todo getSnapshot
});
return <div>...</div>;
}
二、什么时候该使用什么Hooks?
- 状态管理useState VS useReducer
这个非常简单,就好比页面是否需要引入redux,页面只需要要简单状态useState,页面状态比较复杂需要状态管理useReducer - 缓存处理useCallback vs useMemo vs React.memo,目的都是为了缓存提高性能,它们区别:
- React.memo前面已经介绍了类似shouldComponentUpdate功能,可以控制组件是否更新,可以参照生命周期中示例。
- useCallback返回一个缓存的函数,在函数式编程中使用剪头符号创建函数,如果不使用useCallback则每次都会返回一个新函数对象增加不必要的渲染
const handleSubmit = useCallback(data => {
console.log(data);
}, []);
return (
<div className="form-test">
<AForm initialValue={data} onSubmit={handleSubmit}/>
<button className="submit" form={form.id} type="submit">表单提交</button>
</div>
);
- useMemo返回一个缓存的对象,和useCallback用法一样,一般用作数据处理。
const columns = React.useMemo(() => {
return columnsFactory(keys, fields, child, handleClick);
}, [fields]);
3. 副作用useEffect vs useLayoutEffect vs useMutationEffect(removed)
浏览器渲染过程- useMutationEffect
同步阻塞,触发动作发生在重排 layout 前,DOM结构已经发生了变化。但不能读取重排信息。不过该hook由于一些问题已经被官方移除:github.com/facebook/re… - useLayoutEffect
同步阻塞,触发动作发生在重排 layout 后但在重绘 paint 前,DOM结构已经发生了变化,这时可以从DOM中读取DOM的layout结果比如元素的高度宽度等重排信息。一般可以用来解决页面闪动问题。
useLayoutEffect(() => {
form.register({ name });
form.setValue(name, state);
return () => {
form.unregister(name);
};
}, [form, name]);
- useEffect
异步非阻塞,在渲染commit后,也就是完全在浏览器DOM变化渲染完成后,其实大部分的情况下你应该使用这个hook。
使用useLayoutEffect在服务端渲染会报大量的warning,所以要避免在SSR组件中使用。
三、Hooks特殊feature
即使不发生任何变化也能更新当前组件
import React, { useState } from 'react';
const useForceUpdate = () => useState()[1];
const App = () => {
const forceUpdate = useForceUpdate();
console.log('rendering');
return <button onClick={forceUpdate}>Click To Render</button>;
};
四、自定义Hooks实践
把状态存储在localStoreage中,useStorageState
import { useState } from 'react';
function useStorageState<T>(storage: Storage, key: string, defaultValue?: T | (() => T)) {
const [state, setState] = useState<T | undefined>(() => {
const raw = storage.getItem(key);
if (raw !== null) {
return JSON.parse(raw);
}
if (typeof defaultValue === 'function') {
return (defaultValue as () => T)();
}
return defaultValue;
});
function updateState(value?: T) {
if (typeof value === 'undefined') {
storage.removeItem(key);
setState(defaultValue);
} else {
storage.setItem(key, JSON.stringify(value));
setState(value);
}
}
return [state, updateState];
}
export default useStorageState;
包装useReducer实现多状态支持字段切面,useMultipleState with AOP
import * as React from 'react';
import { getStorage, setStorage } from '../../utils/storage';
declare type kv = Record<string, any>;
declare type func = (value: any) => void;
declare type hook = [any, (state: any) => void];
declare type aop = {
getInitState: (initValue: any) => any;
setState: (setValue: func) => func;
};
export default function useMultipleState(initState: kv, aops: {[key: string]: aop} = {}): hook {
const initStateAop = {};
Object.keys(initState).forEach(key => {
const aop = aops[key] || defaultAop;
initStateAop[key] = aop.getInitState(initState[key]);
});
const reducer = (state: any, newState: any) => ({...state, ...newState});
const [state, dispatch] = React.useReducer(reducer, initStateAop);
const setState = (newState: any) => {
Object.keys(newState).forEach(key => {
const aop = aops[key] || defaultAop;
aop.setState(value => newState[key] = value)(newState[key]);
});
dispatch(newState);
};
return [ state, setState];
}
export function createStorageAop(storageKey: string, dataKey?: string): aop {
return {
getInitState: initValue => {
if (dataKey) {
const dataValue = getStorage(storageKey, initValue[dataKey]);
return {...initValue, [dataKey]: dataValue };
} else {
return getStorage(storageKey, initValue);
}
},
setState: setValue => value => {
setValue(value);
const storageValue = dataKey ? value[dataKey] : value;
setStorage(storageKey, storageValue);
}
};
}
const defaultAop: aop = {
getInitState: initValue => initValue,
setState: setState => setState,
};
五、社区一些Hooks
Hooks可以很大程度上简化代码开发,社区已经有很多的hook类库,这里有一个收集Hooks的仓库,这里介绍几个:
useArray,简化数组操作
const App = () => {
const todos = useArray(['hello', 'world', 'hooks']);
return (
<div>
<h3>Todos</h3>
<button onClick={() => todos.add(Math.randown())}>add</button>
<url>
{todos.value.map((todo, i) => {
<li key={i}>
{todo}
<button onClick={() => todos.removeIndex(i)}>delete</button>
</li>
})}
</ul>
<button onClick={todos.clear}>clear todos</button>
</div>
);
}
useFetch,简化异步请求
import React from "react";
import useFetch from "react-fetch-hook";
const Component = () => {
const { isLoading, data } = useFetch("https://swapi.co/api/people/1");
return isLoading ? (
<div>Loading...</div>
) : (
<UserProfile {...data} />
);
};
useOnClickOutside
import React from 'react'
import useOnClickOutside from 'use-onclickoutside'
export default function Modal({ close }) {
const ref = React.useRef(null)
useOnClickOutside(ref, close)
return <div ref={ref}>{'Modal content'}</div>
}
六、Hooks do more
我在想Hooks是不是能做一些复杂的事情,所以动态表单Aform hook诞生了 @alife/use-aform,主要需要实现的能力
- 拓展配置
- 结构渲染
- 取值赋值
- 表单验证
- 数据监听
- 组件联动
useAform使用简单示例
import * as React from 'react';
import useAform from '@alife/use-aform';
const aformSchema = {...}; // aform结构
const initData = {...}; // 初始化数据
function App() {
const { useCallback } = React;
const { form, AForm } = useAform(aformSchema);
const title = form.watch('title');
const handleSubmit = useCallback(data => {
console.log(data);
// form.setValue(); form.getValue();
}, []);
return (
<div className="form-wrap">
<h3>{title}</h3>
<AForm initialValue={initData} onSubmit={handleSubmit}/>
<button className="submit" form={form.id} type="submit">表单提交</button>
</div>
);
}
ReactDOM.render(<App />, mountNode);