其实react.memo的实现很简单,就几行代码。html
export default function memo<Props>(
type: React$ElementType,
compare?: (oldProps: Props, newProps: Props) => boolean,
) {
if (__DEV__) {
if (!isValidElementType(type)) {
warningWithoutStack(
false,
'memo: The first argument must be a component. Instead ' +
'received: %s',
type === null ? 'null' : typeof type,
);
}
}
return {
$$typeof: REACT_MEMO_TYPE,
type,
compare: compare === undefined ? null : compare,
};
}
复制代码
能够看到,最终返回的是一个对象,这个对象带有一些标志属性,在react Fiber的过程当中会作相应的处理。前端
在ReactFiberBeginWork.js中能够看到:react
if (updateExpirationTime < renderExpirationTime) {
// This will be the props with resolved defaultProps,
// unlike current.memoizedProps which will be the unresolved ones.
const prevProps = currentChild.memoizedProps;
// Default to shallow comparison
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
复制代码
根据传入的compare
函数比较prevProps
和nextProps
,最终决定生成对象,并影响渲染效果。git
其实在这以前,早已经有一个生命周期函数实现了相同的功能。他就是shouldComponentUpdate
。es6
之因此再增长这个memo
,也是react团队一直在秉承的信念。那就是让一切变得更加函数式
。github
经过一个例子来看看memo如何使用。编程
先建立一个简单组件SubComponent
。后端
const SubComponent = props =>
<>
i am {props.name}. hi~
</>
复制代码
调用React.memo建立memo组件api
const Memo = React.memo(SubComponent, (prevProps, nextProps) =>
prevProps.name === nextProps.name
);
复制代码
在页面上调用memopromise
<div className="App">
<Memo name={name} />
</div>
复制代码
memo
接收两个参数,一个是组件,一个是函数。这个函数就是定义了memo需不须要render的钩子。
比较前一次的props跟当前props,返回true表示不须要render。
也就是传给Memo的name不变时,不会触发SubComponent的render函数。
当前页面上的SubComponent仍是以前的,并无从新渲染。这也是为啥叫memo的缘由吧。
React.lazy 用于作Code-Splitting
,代码拆分。相似于按需加载,渲染的时候才加载代码。
用法以下:
import React, {lazy} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
复制代码
lazy(() => import('./OtherComponent'))
使用es6的import()返回一个promise,相似于:
lazy(() => new Promise(resolve =>
setTimeout(() =>
resolve(
// 模拟ES Module
{
// 模拟export default
default: function render() {
return <div>Other Component</div>
}
}
),
3000
)
));
复制代码
React.lazy的提出是一种更优雅的条件渲染解决方案。
之因此说他更优雅,是由于他将条件渲染的优化提高到了框架层。
这里咱们引出suspense。
当咱们组件未渲染完成,须要loading时,能够这么写:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
复制代码
在咱们的业务场景中,OtherComponent能够表明多个条件渲染组件,咱们所有加载完成才取消loding。
只要promise没执行到resolve,suspense都会返回fallback中的loading。
代码简洁,loading可提高至祖先组件,易聚合。至关优雅的解决了条件渲染。
关于suspense的异步渲染原理有篇文章写的很好,感兴趣的在文末查看。
hooks提出有一段时间了,dan也一直在推广,而且表示很快加入react正式版本。
关于一些介绍,直接看官网会更好。
hooks经常使用api有:useState、useEffect、useContext、useReducer、useRef
等。
主要操做一下useEffect
,用处很大。触类旁通。
当all is function
,没了component,天然也没了各类生命周期函数,此时useEffect
登场。
下面经过一个组件实例来讲明。
影像组件,功能有:前端加水印、实现拖拽。
大体实现以下:
class ImageModal extends Component {
constructor(props) {
...
}
componentDidMount() {
// 画水印、注册拖拽事件逻辑
// 以及其余的image处理相关逻辑
}
componentDidUpdate(nextProps, prevProps) {
if (nextProps.cur !== prevProps.cur) {
// 切换时重置状态(好比 旋转角度、大小等)逻辑
// image特有逻辑
}
}
render() {
return <>
...
<img ... />
</img>
}
}
复制代码
ImageModal负责渲染图片modal,如今有另外一个modal用来渲染html模板。
命名为HtmlModal,HtmlModal接受后端返回的html,通过处理后内嵌在网页中。
一样要求加水印、拖拽的功能等。
也就是image跟html有部分逻辑相同有部分不相同。
基于这个考虑,再写一个组件。
同理实现以下:
class HtmlModal extends Component {
constructor(props) {
...
}
componentDidMount() {
// 画水印、注册拖拽事件逻辑
// 以及其余的html处理相关逻辑
}
componentDidUpdate(nextProps, prevProps) {
if (nextProps.cur !== prevProps.cur) {
// 切换时重置状态(好比 旋转角度、大小等)逻辑
// html特有逻辑
}
}
render() {
return <>
...
<div dangerouslySetInnerHTML={{ __html: ... }}></div>
</img>
}
}
复制代码
能够看到HtmlModal
和ImageModal
在componentDidMount
和componentDidUpdate
周期中有很多逻辑是相同的。
若是咱们使用useEffect
的话,能够怎么实现这个复用和分离呢?来看看。
export function useMoveEffect() {
// 第二个参数传了固定值 []
// 至关于 componentDidMount
useEffect(() => {
// 实现拖拽逻辑
}, []);
}
export function useDrawMarkEffect(cur) {
useEffect(() => {
// 实现水印逻辑
}, []);
}
export function useResetEffect(cur); {
// 第二个参数传了固定值 [ cur ]
// 至关于 componentDidUpdate 比较 cur
useEffect(() => {
// 实现重置逻辑
}, [ cur ]);
}
function useOtherImageEffect(...) {
useEffect(() => {
// 实现image特有逻辑
}, [ ... ]);
}
function ImageModal (props) {
// 细分 Effect,方便复用
useMoveEffect();
useDrawMarkEffect();
useResetEffect(props.cur);
...
useOtherImageEffect(...);
return <>
...
<img ... />
</img>
}
复制代码
ok,有了上面的梳理和useEffect
重构,咱们来编写HtmlModal
:
import { useMoveEffect, useDrawMarkEffect, useResetEffect } from './imageModal'
function useOtherHtmlEffect(...) {
useEffect(() => {
// 实现html特有逻辑
}, [ ... ]);
}
function HtmlModal (props) {
// 细分 Effect,方便复用
useMoveEffect();
useDrawMarkEffect();
useResetEffect(props.cur);
...
useOtherHtmlEffect(...);
return <>
...
<img ... />
</img>
}
复制代码
以上,实现了生命周期中重复逻辑的复用。之后不管新增什么modal,均可以复用逻辑,摆脱了 ctr c/ ctr v
。
从而组件变得更小、代码变得简洁,提高编程体验。
参考资料: React v16.6.0: lazy, memo and contextType(官网)
Making Sense of React Hooks(dan)
React Suspense(中文)
以为有帮助的点个赞,甚至能够关注一波哦~