一 React 新特性一览
01 contextType 用来替代 <BatteryContext.Consumer>
class Leaf extends Component {
static contextType = BatteryContext;
render() {
const battery = this.context;
return (
<h1>Battery: {battery}</h1>
);
}
}
02 Lazy 与 Suspense
延迟加载
- webpack - code splitting 代码拆分
- import
react lazy封装了组件导入行为
import React, { Component, lazy, Suspense } from "react";
const About = lazy(() => import(/* webpackChunkName: "about" */ "./About"));
// ErrorBoundary 错误边界
// componentDidCatch or getDerivedStateFromError
class App extends Component {
state = {
hasError: false,
};
static getDerivedStateFromError() {
return {
hasError: true,
};
}
render() {
if (this.state.hasError) {
return <div>error</div>;
}
return (
<div>
<Suspense fallback={<div>loading</div>}>
<About />
</Suspense>
</div>
);
}
}
export default App;
03 Memo 指定组件渲染
PureComponent可优化浅比较渲染,但深层数据变化无法识别,容易触发视图不更新bug
class Foo extends PureComponent {
render(){
console.log('Foo render');
return null;
}
}
修复视图不更新bug
callback = () => {}
//...
<Foo person={person} cb={this.callback} />
每次都向Foo传入新的cb函数,从而触发Foo更新
function形态组件
const Foo = memo(function Foo(props) {
console.log("Foo render");
return <div>{props.person.age}</div>;
});
二 颠覆性新特性Hooks
优化了类组件的三大问题
- 函数组件无this问题
- 自定义Hook方法复用状态逻辑
- 副作用的关注点分离
01 State Hooks
一个类组件
class App extends Component {
state = {
count: 0,
};
render() {
const { count } = this.state;
return (
<button
type="button"
onClick={() => {
this.setState({ count: count + 1 });
}}
>
Click ({count})
</button>
);
}
}
一个Hook组件
function App() {
const [count, setCount] = useState(0);
return (
<button
type="button"
onClick={() => {
setCount(count + 1);
}}
>
Click ({count})
</button>
);
}
useState严格要求调用,开发中易出错
可使用eslint-plugin-react-hooks 来避免此问题发生
- 安装
npm i eslint-plugin-react-hooks -D - 配置eslint
"eslintConfig": {
"extends": "react-app",
"plugins": [
"react-hooks"
],
"rules": {
"react-hooks/rules-of-hooks": "error"
}
},
- 重启
性能优化
function App(props) {
// const defaultCount = props.defaultCount || 0;
const [count, setCount] = useState(() => {
// 优化props数据性能,此函数只运行一次
console.log('initial count');
return props.defaultCount || 0;
});
02 Effect Hooks
类组件副作用时机
- Mount 之后
- Update 之后
- Unmount 之前
现在使用useEffect,useEffect会在render之后调用
一个类组件生命周期
componentDidMount(){
document.title = this.state.count;
window.addEventListener('resize', this.onResize, false);
}
componentWillUnmount(){
window.removeEventListener('resize', this.onResize, false);
}
componentDiUpdate(){
document.title = this.state.count;
}
一个Hook Effect
useEffect(() => {
document.title = count;
});
useEffect(() => {
window.addEventListener("resize", onResize, false);
// 重渲染或卸载时调用
return () => {
window.removeEventListener("resize", onResize, false);
};
}, []);
第二个参数 [],每项都不变才会阻止useEffect执行。
- 如果不传数组,意味着每次渲染都执行useEffect
- 传空数组,useEffect只在第一次渲染执行一次
- 下列只在count变化后才会执行useEffect
useEffect(()=>{
console.log('count:', count);
}, [count])
03 Context Hooks
一段原始Consumer
const CountContext = createContext();
class Foo extends Component {
render() {
return (
<CountContext.Consumer>
{(count) => <h1>{count}</h1>}
</CountContext.Consumer>
);
}
}
<CountContext.Provider value={count}>
<Foo />
</CountContext.Provider>
一段 contextType 版 Consumer
const CountContext = createContext();
class Bar extends Component {
static contextType = CountContext;
render() {
const count = this.context;
return <h1>{count}</h1>;
}
}
<CountContext.Provider value={count}>
<Bar />
</CountContext.Provider>
一段Hook 版 useContext
const CountContext = createContext();
function Counter() {
const count = useContext(CountContext);
return <h1>{count}</h1>;
}
<CountContext.Provider value={count}>
<Counter />
</CountContext.Provider>
04 Memo&Callback Hooks
// 渲染间完成, 第二个参数规则同useEffect
const double = useMemo(() => {
return count * 2;
}, [count]);
// 第二个参数空数组,只渲染一次,以此优化onClick性能
const onClick = useMemo(()=>{
return () => {
console.log('Click')
}
},[])
// 等价 useMemo返回函数情况 useMemo(() => fn)
const onClick = useCallback(()=>{
console.log('Click')
},[])
useCallback优化
const [ClickCount, setClickCount] = useState(1);
const onClick = useCallback(()=>{
console.log('Click')
setClickCount(ClickCount + 1);
},[ClickCount])
// 可省略数组中更新依赖,等价上面
const onClick = useCallback(()=>{
console.log('Click')
setClickCount((ClickCount) => ClickCount + 1);
},[])
05 Ref Hooks
使用场景一 调用类组件内的方法 或 DOM
const onClick = useCallback(() => {
// 通过 ref 调用类组件内的方法
counterRef.current.speak();
}, [counterRef]);
使用场景二 避免it每次渲染重赋值(需要访问上次渲染数据)
const it = useRef();
// init启动setInterval 每秒+1
useEffect(() => {
it.current = setInterval(()=>{
setCount(Count => Count + 1);
}, 1000)
}, [])
// 每次渲染都查看count值
useEffect(()=>{
if(count >= 10){
clearInterval(it.current)
}
})
06 自定义Hooks 状态逻辑的复用
使用自定义Hooks 抽离count逻辑
function useCount(defalultCount) {
const [count, setCount] = useState(defalultCount);
const it = useRef();
useEffect(() => {
it.current = setInterval(() => {
setCount((Count) => Count + 1);
}, 1000);
}, []);
useEffect(() => {
if (count >= 10) {
clearInterval(it.current);
}
});
return [count, setCount];
}
function App(props) {
const [count, setCount] = useCount(0);
// ...
}
另一个例子
function useSize() {
const [Size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
});
// 每次resize都传递新的函数, 导致性能问题
// 通过使用useCallback 缓存箭头函数,避免不必要的性能消耗
const onResize = useCallback(() => {
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
});
}, []);
useEffect(() => {
window.addEventListener("resize", onResize, false);
return () => {
window.removeEventListener("resize", onResize, false);
};
}, []);
return Size;
}
function App(props) {
const size = useSize();
return (<div>{size.width}*{size.height}</div>)
}
function Count(props) {
const size = useSize();
return (<div>{size.width}*{size.height}</div>)
}
07 Hooks的使用法则
一 只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook
二 只在 React 函数中调用 Hook
不要在普通的 JavaScript 函数中调用 Hook
08 Hooks的常见问题
一 生命周期对应
根据props 变化更新state
class Counter extends Component {
state = {
overflow: false,
};
static getDerivedStateFromProps(props, state){
if(props.count > 10){
return {
overflow: true
}
}
}
}
// 等价函数写法
function Counter(props){
const [overflow, setOverflow] = useState(false);
if(props.count > 10){
setOverflow(true);
}
}
function App(){
useEffect(()=>{
// componentDidMount
return ()=>{
// componentWillUnmount
}
}, []);
let renderCounter = useRef(0);
renderCounter.current++;
useEffect(()=>{
if(renderCounter > 1){
// componentDidUpdate
}
})
}
二 获取历史props或state
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return (
<h1>
Now: {count}, before: {prevCount}
</h1>
);
}
三 强制更新Hooks组件
function Counter() {
const [Updater, setUpdater] = useState(0);
function forceUpdate() {
setUpdater((Updater) => Updater + 1);
}
}