- purecomponent和componment
- Context
- 错误边界
- Refs 转发
- Portals
- Profiler
- Render Props
- Concurrent 模式
PureComponent通过prop和state的浅比较来实现shouldComponentUpdate。浅比较(shallowEqual),即react源码中的一个函数。 判断checkShouldComponentUpdate:
function checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) { .... .... if (ctor.prototype && ctor.prototype.isPureReactComponent) { return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState); } return true; } 复制代码
function shallowEqual(objA, objB) { if (is(objA, objB)) { return true; } if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { return false; } var keysA = Object.keys(objA); var keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } // Test for A's keys different from B. for (var i = 0; i < keysA.length; i++) { if (!hasOwnProperty$1.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { return false; } } return true; } 复制代码
先来看下这个例子:
export default class Cheap extends PureComponent { state = { show: false }; changeShow = () => { this.setState({ show: true }); } render() { console.log('again render'); return ( <div> <h2>ReactDetail: </h2> <button className={S.button} onClick={this.changeShow}>点击修改state</button> {this.state.show && <div>展示show</div>} </div> ); } } 复制代码
再看另外一个例子:
export default class Cheap extends PureComponent { state = { arr: ['1'] }; changeShow = () => { const { arr } = this.state; arr.push('2'); this.setState({ arr }); } // changeState = () => { // const { arr } = this.state; // this.setState({ // arr: [...arr, '2'] // }); // }; render() { console.log('again render'); return ( <div> <h2>ReactDetail: </h2> <button className={S.button} onClick={this.changeShow}>点击修改state</button> <div> {this.state.arr.map((item) => item)} </div> </div> ); } } 复制代码
PureComponent中浅比较这个数组的引用没有变化所以没有渲染。this.setState()以后,值是在render的时候更新的。
结论:PureComponent不仅会影响本身,而且会影响子组件,所以PureComponent最佳情况是展示组件
Context设计目的是为了共享那些对于一个组件树而言是“全局”的数据,使用Context, 我们可以避免通过中间元素传递props。
const MyContext = React.createContext(defaultValue); 生成如下的内容:
<MyContext.Provider value={{ count1, fn }}> <B /> </MyContext.Provider> 复制代码
content使用的例子
const MyContext = createContext(null); class MyClass extends React.Component { static contextType = MyContext; componentDidMount() { // const value = this.context; // console.log(value); console.log(this.context); this.context.fn(); /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */ } render() { // const value = this.context; // console.log(value); console.log(this.context); return <div />; } } // MyClass.contextType = MyContext; function MyFunction() { return ( <MyContext.Consumer> { value => ( <> <h1 >{value.count}</h1> <button className={S.button1} onClick={() => { value.fn(); }}>函数型组件</button> </> ) } </MyContext.Consumer> ); } export default class Cheap extends Component { state = { count: 0, }; componentDidMount() {} changeState = () => { const { count } = this.state; this.setState({ count: count + 1, }); }; render() { const { count } = this.state; const fn = () => { this.setState({ count: count + 1, }); console.log('test'); }; return ( <div> <h2>ReactDetail: </h2> <button className={S.button1} onClick={this.changeState}> 点击修改count {count} </button> <MyContext.Provider value={{ count, fn }}> <MyClass /> <p className={S.fenge} /> <MyFunction /> </MyContext.Provider> </div> ); } } 复制代码
源码:
function updateContextConsumer(current$$1, workInProgress, renderExpirationTime) { var context = workInProgress.type; ...... console.log(2121212121); // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; reconcileChildren(current$$1, workInProgress, newChildren, renderExpirationTime); return workInProgress.child; } 复制代码
如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // 更新 state 使下一次渲染能够显示降级后的 UI return { hasError: true }; } componentDidCatch(error, errorInfo) { // 你同样可以将错误日志上报给服务器 logErrorToMyService(error, errorInfo); } render() { if (this.state.hasError) { // 你可以自定义降级后的 UI 并渲染 return <h1>Something went wrong.</h1>; } return this.props.children; } } <ErrorBoundary> <MyWidget /> </ErrorBoundary> 复制代码
注意:错误边界仅可以捕获其子组件的错误
refs转发主要是能够直接访问子组件的子组件实例或者Dom节点
function A() { const click = () => { console.log(1); }; return <div onClick={click}>组件A</div>; } class B extends Component { a = () => { console.log('B组件'); } render() { return ( <div> 组件B </div> ); } } export default class Cheap extends Component { componentDidMount() { // this.$input.focus(); console.log(this.$input); // console.log($(this.$input).width()); console.log(this.$A); console.log(this.$B); } render() { return ( <div> <A ref={c => { this.$A = c; }} /> <B ref={c => { this.$B = c; }} /> <input ref={c => { this.$input = c; }} placeholder="一起" /> </div> ); } } 复制代码
你不能在函数组件上使用 ref 属性,因为它们没有实例
再看下面的例子
class C extends Component { render() { const { forwardedRef, ...rest } = this.props; return ( <div ref={forwardedRef} {...rest}> 组件C </div> ); } } const D = React.forwardRef((props, ref) => <C {...props} forwardedRef={ref} />); export default class Cheap extends Component { componentDidMount() { console.log(this.$D); console.log(this.$refD.current); } $refD = React.createRef() render() { return ( <div> <A ref={c => { this.$A = c; }} /> <B ref={c => { this.$B = c; }} /> <D ref={c => { this.$D = c; }} /> <D ref={this.$refD} /> <input ref={c => { this.$input = c; }} placeholder="一起" /> </div> ); } } 复制代码
第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。
Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中。
#####用法: 将子节点渲染到存在于父组件以外的 DOM
一般的情况下,该元素将被挂载到 DOM 节点中离其最近的父节点
render() { // React 挂载了一个新的 div,并且把子元素渲染其中 return ( <div> {this.props.children} </div> ); } 复制代码
function Protal() { const childClick = () => { console.log('child'); }; return <div className={S.protal} onClick={childClick}>1</div>; } class Container extends Component { el = document.createElement('div') render() { return ReactDOM.createPortal( this.props.children, document.getElementById('Page') ); } } export default class Cheap extends Component { componentDidMount() { } $refD = React.createRef() Parent = () => { console.log('parent'); } render() { return ( <div onClick={this.Parent}> <div id="zlg" ref={c => { this.$A = c; }} /> <div>测试Portal</div> <Container> <Protal /> </Container> </div> ); } } 复制代码
结论:Protals提供了一种将组件直接挂载到直接父组件 DOM 层次之外的一类方式,脱离文档流,但是可以一直冒泡到祖先组件
测量渲染一个 React 应用多久渲染一次以及渲染一次的“代价”。
import React, { Component, useState, useEffect, unstable_Profiler as Profiler } from 'react'; function Content() { const [count, setCount] = useState(1); const click = () => { setCount(prv => prv + 1); }; return ( <div onClick={click}> 测试 {count} </div> ); } export default class Cheap extends Component { componentDidMount() { } $refD = React.createRef() Parent = () => { console.log('parent'); } callback = ( id, // 发生提交的 Profiler 树的 “id” phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一 actualDuration, // 本次更新 committed 花费的渲染时间 baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间 startTime, // 本次更新中 React 开始渲染的时间 commitTime, // 本次更新中 React committed 的时间 interactions ) => { console.log(id); console.log(phase); console.log(actualDuration); console.log(baseDuration); console.log(startTime); console.log(commitTime); console.log(interactions); } render() { return ( <div> <Profiler id="Content" onRender={this.callback}> <Content /> </Profiler> </div> ); } } 复制代码
[Profiler性能分析器链接][1]
主要是为了组件的复用性设立的,能够动态决定什么是需要渲染的。提出疑问:与children的区别在哪里?
class RenderA extends Component { render() { return ( <div> child {this.props.render('render')} </div> ); } } export default class Cheap extends Component { render() { return ( <div> <p>test</p> <RenderA render={data => <span>{data}</span>} /> </div> ); } } 复制代码
注意:将 Render Props 与 React.PureComponent 一起使用时要小心。你在 render 方法里创建函数,那么使用 render prop 会抵消使用 React.PureComponent 带来的优势。因为浅比较 props 的时候总会得到 false,并且在这种情况下每一个 render 对于 render prop 将会生成一个新的值。
<RenderE render={list => <Echild list={list}/>} /> 替换成 renderEchild(list) { return <Echild list={list} />; } <RenderE render={this.renderEchild} /> 复制代码
ReactDOM.render(<App />, rootNode)
。这是当前 React app 使用的方式。当前没有计划删除本模式,但是这个模式可能不支持这些新功能。ReactDOM.createBlockingRoot(rootNode).render(<App />)
。目前正在实验中。作为迁移到 concurrent 模式的第一个步骤。ReactDOM.createRoot(rootNode).render(<App />)
。目前在实验中,未来稳定之后,打算作为 React 的默认开发模式。这个模式开启了所有的新功能。<SuspenseList revealOrder="forwards"> <Suspense fallback={'加载中...'}> <ProfilePicture id={1} /> </Suspense> <Suspense fallback={'加载中...'}> <ProfilePicture id={2} /> </Suspense> <Suspense fallback={'加载中...'}> <ProfilePicture id={3} /> </Suspense> ... </SuspenseList> 复制代码
Suspense 接受两个 props:
SuspenseList 接受两个 props:
const SUSPENSE_CONFIG = { timeoutMs: 2000 }; const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG); 复制代码
useTransition允许组件在切换到下一个界面之前等待内容加载,从而避免不必要的加载状态。它还允许组件将速度较慢的数据获取更新推迟到随后渲染,以便能够立即渲染更重要的更新。
const OtherComponent = lazy(() => new Promise(resolve => setTimeout(resolve, 2000)).then( () => import('./common.js') )); const OtherComponent1 = lazy(() => new Promise(resolve => setTimeout(resolve, 4000)).then( () => import('./common1.js') )); export default class cheap extends Component { render() { return ( <div> <Suspense fallback={<h1>Loading profile...</h1>}> <OtherComponent /> <Suspense fallback={<h1>Loading profile111...</h1>}> <OtherComponent1 /> </Suspense> </Suspense> </div> ); } } 复制代码