React高级用法

1.高阶组件

高阶组件是React中用于复用组件逻辑的一种高级技巧。
简单的说,就是组件作为参数,返回值也是组件的函数,它是纯函数,不会修改传入的组件,也不会使用继承复制其行为。HO通过将组件包装在容器组件中来组成新组件。

1.1 使用HOC的原因

1.抽取重复代码,实现组件复用:相同功能组件复用
2.条件渲染,控制组件的渲染逻辑(渲染劫持):权限控制。
3.捕获/劫持被处理组件的生命周期,常见场景:组件渲染性能追踪、日志打点

1.2 HOC实现方式

1.2.1 属性代理

使用组合的方式,将组件包装在容器上,依赖父子组件的生命周期关系来
1.返回stateless的函数组件
2.返回class组件

  • 操作props
// 可以通过属性代理,拦截⽗组件传递过来的porps并进⾏处理。
// 返回⼀个⽆状态的函数组件
function HOC(WrappedComponent) {
  const newProps = { type: 'HOC' };
  return props => <WrappedComponent {...props} {...newProps}/>;
}
// 返回⼀个有状态的 class 组件
function HOC(WrappedComponent) {
  return class extends React.Component {
    render() {
      const newProps = { type: 'HOC' };
      return <WrappedComponent {...this.props} {...newProps}/>;
   }
 };
}
  • 抽象state
// 通过属性代理⽆法直接操作原组件的state,可以通过props和cb抽象state
function HOC(WrappedComponent) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        name: '',
     };
      this.onChange = this.onChange.bind(this);
   }
    
    onChange = (event) => {
      this.setState({
        name: event.target.value,
     })
   }
    
    render() {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onChange,
       },
     };
      return <WrappedComponent {...this.props} {...newProps} />;
   }
 };
}
// 使⽤ @HOC
class Example extends Component {
  render() {
    return <input name="name" {...this.props.name} />;
 }
}
  • 通过props实现条件渲染
// 通过props来控制是否渲染及传⼊数据
import * as React from 'react';
function HOC (WrappedComponent) {
  return (props) => (
  <div>
   {
      props.isShow ? (
        <WrappedComponent
         {...props}
        />
     ) : <div>暂⽆数据</div>
   }
  </div>
 );
}
export default HOC; 
  • 其他元素wrapper传⼊的组件
function withBackgroundColor(WrappedComponent) {
  return class extends React.Component {
    render() {
      return (
        <div style={{ backgroundColor: '#ccc' }}>
            <WrappedComponent {...this.props} {...newProps} />
        </div>
     );
   }
 };
}
1.2.2 反向继承

使⽤⼀个函数接受⼀个组件作为参数传⼊,并返回⼀个继承了该传⼊组件的类组件,且在返回组件的 render() ⽅法中返回 super.render() ⽅法

const HOC = (WrappedComponent) => {
  return class extends WrappedComponent {
    render() {
      return super.render();
   }
 }
}
  1. 允许HOC通过this访问到原组件,可以直接读取和操作原组件的state/ref等;
  2. 可以通过super.render()获取传⼊组件的render,可以有选择的渲染劫持;
  3. 劫持原组件⽣命周期⽅法
function HOC(WrappedComponent){
  const didMount = WrappedComponent.prototype.componentDidMount;
  
  // 继承了传⼊组件
  return class HOC extends WrappedComponent {
    async componentDidMount(){
      // 劫持 WrappedComponent 组件的⽣命周期
      if (didMount) {
        await didMount.apply(this);
     }
      ...
   }
    render(){
      //使⽤ super 调⽤传⼊组件的 render ⽅法
      return super.render();
   }
 }
}
  • 读取/操作原组件的state
function HOC(WrappedComponent){
  const didMount = WrappedComponent.prototype.componentDidMount;
  // 继承了传⼊组件
  return class HOC extends WrappedComponent {
    async componentDidMount(){
      if (didMount) {
        await didMount.apply(this);
     }
      // 将 state 中的 number 值修改成 2
      this.setState({ number: 2 });
   }
    render(){
      //使⽤ super 调⽤传⼊组件的 render ⽅法
      return super.render();
   }
 }
}
  • 条件渲染
const HOC = (WrappedComponent) =>
  class extends WrappedComponent {
    render() {
      if (this.props.isRender) {
        return super.render();
     } else {
        return <div>暂⽆数据</div>;
     }
   }
 }
  • 修改react树
// 修改返回render结果
function HigherOrderComponent(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      const tree = super.render();
      const newProps = {};
      if (tree && tree.type === 'input') {
        newProps.value = 'something here';
     }
      const props = {
        ...tree.props,
        ...newProps,
     };
      const newTree = React.cloneElement(tree, props,
tree.props.children);
      return newTree;
   }
 };
}
1.2.3 属性代理和反向继承

属性代理: 从‘组合’角度出发,有利于从外部操作wrappedComp,可以操作props,或者在wrappedComp外加一些拦截器(如条件渲染等)

反向继承:从‘继承’角度出发,从内部操作wrappedComp,可以操作组件内部的state,生命周期和render。

2.Hooks详解

Hooks是react16.8以后新增的钩子API;
目的:增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。

为什么要使用?
1.开发友好,可拓展性强。抽离公共的方法或组件,Hook使你在无需修改组件结构的情况下复用状态逻辑
2.函数式编程,将组件中相关联的部分根据业务逻辑拆分成更小的函数
3.class更多作为语法糖,没有稳定的提案,且在开发过程中会出现不必要的优化点,Hooks⽆需学习 复杂的函数式或响应式编程技术

2.1 常见的Hooks

2.1.1 useState
2.1.2 useEffect

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

1.当组件init、dom render完成、操纵dom、请求数据(如componentDidMount)等;
2. 不限制条件,组件每次更新都会触发useEffect --> componentDidUpdate 与 componentwillreceiveprops;
3. useEffect 第⼀个参数为处理事件,第⼆个参数接收数组,为限定条件,当数组变化时触发事件, 为[]只在组件初始化时触发;
4. useEffect第⼀个参数接收返回函数,⽤以消除某些副作⽤ --> componentWillUnmount

2.1.3 useLayoutEffect

作⽤渲染更新之前的 useEffect

useEffect: 组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执⾏useEffect回调 ;

useLayoutEffect : 组件更新挂载完成 -> 执⾏useLayoutEffect回调-> 浏览器dom 绘制完成;

渲染组件 1. useEffect:闪动; 2. useLayoutEffect:卡顿;

const DemoUseLayoutEffect = () => {
    const target = useRef()
    useLayoutEffect(() => {
        /*我们需要在dom绘制之前,移动dom到制定位置*/
        const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */
        animate(target.current,{ x,y })
   }, []);
    return (
        <div >
            <span ref={ target } className="animate"></span>
        </div>
   )
}
2.1.4 useRef

⽤来获取元素、缓存数据; ⼊参可以作为初始值

// 获取元素
const DemoUseRef = ()=>{
    const dom= useRef(null)
    const handerSubmit = ()=>{
        /* <div >表单组件</div> dom 节点 */
        console.log(dom.current)
   }
    return <div>
       {/* ref 标记当前dom节点 */}
        <div ref={dom} >表单组件</div>
        <button onClick={()=>handerSubmit()} >提交</button>
    </div>
}
// 缓存数据
// 不同于useState,useRef改变值不会使comp re-render
const currenRef = useRef(InitialData)
currenRef.current = newValue
2.1.5 useContext

⽤来获取⽗级组件传递过来的context值,这个当前值就是最近的⽗级组件 Provider 的value;

从parent comp获取ctx⽅式;

  1. useContext(Context)
  2. Context.Consumer
/* ⽤useContext⽅式 */
const DemoContext = ()=> {
    const value = useContext(Context);
    /* my name is aaa */
 return <div> my name is { value.name }</div>
}
/* ⽤Context.Consumer ⽅式 */
const DemoContext1 = ()=>{
    return <Context.Consumer>
         {/* my name is aaa */}
       { (value)=> <div> my name is { value.name }</div> }
    </Context.Consumer>
}
export default ()=>{
    return <div>
      <Context.Provider value={{ name:'aaa' }} >
        <DemoContext />
        <DemoContext1 />
        </Context.Provider>
  </div>
}
2.1.6 useReducer

类似redux
入参:

  1. 第⼀个为函数,可以视为reducer,包括state 和 action,返回值为根据action的不同⽽改变后的 state
  2. 第⼆个为state的初始值

出参:
1.第⼀个更新后的state值
3. 第⼆个是派发更新的dispatch函数;执⾏dispatch会导致组件re-render(另⼀个是useState)

const DemoUseReducer = ()=>{
 /* number为更新后的state值, dispatchNumbner 为当前的派发函数 */
 const [ number , dispatchNumbner ] = useReducer((state, action) => {
  const { payload , name } = action
  /* return的值为新的state */
  switch(name) {
  case 'a':
    return state + 1
  case 'b':
    return state - 1
  case 'c':
   return payload   
 }
  return state
 }, 0)
 return <div>
   当前值:{ number }
  { /* 派发更新 */ }
   <button onClick={()=>dispatchNumbner({ name: 'a' })} >增加</button>
   <button onClick={()=>dispatchNumbner({ name: 'b' })} >减少</button>
   <button onClick={()=>dispatchNumbner({ name: 'c' , payload:666 })}
>赋值</button>
  { /* 把dispatch 和 state 传递给⼦组件 */ }
   <MyChildren  dispatch={ dispatchNumbner } State={{ number }} />
 </div>
}

业务中经常将 useReducer+useContext 代替Redux

2.1.7 useMemo

用来根据useMemo的第二个参数deps(数组)判定是否满足当前的限定条件来确定是否执行第一个cb

// selectList 不更新时,不会重新渲染,减少不必要的循环渲染
useMemo(() => (
 <div>{
  selectList.map((i, v) => (
   <span
    className={style.listSpan}
    key={v} >
   {i.patentName}
   </span>
 ))}
 </div>
), [selectList])
————————————————————————————————————————————————————
// listshow, cacheSelectList 不更新时,不会重新渲染⼦组件
useMemo(() => (
 <Modal
  width={'70%'}
  visible={listshow}
  footer={[
   <Button key="back" >取消</Button>,
   <Button
     key="submit"
     type="primary"
   >
     确定
   </Button>
 ]}
 >
 { /* 减少了PatentTable组件的渲染 */ }
  <PatentTable
   getList={getList}
   selectList={selectList}
   cacheSelectList={cacheSelectList}
   setCacheSelectList={setCacheSelectList}
  />
 </Modal>
), [listshow, cacheSelectList])
————————————————————————————————————————————————————
// 减少组件更新导致函数重新声明
const DemoUseMemo = () => {
 /* ⽤useMemo 包裹之后的log函数可以避免了每次组件更新再重新声明 ,可以限制上下⽂的
执⾏ */
 const newLog = useMemo(() => {
  const log = () => {
   console.log(123)
 }
  return log
}, [])
 return <div onClick={()=> newLog() } ></div>
}
————————————————————————————————————————————————————
// 如果没有加相关的更新条件,是获取不到更新之后的state的值的
const DemoUseMemo = () => {
 const [ number ,setNumber ] = useState(0)
 const newLog = useMemo(() => {
  const log = () => {
   /* 点击span之后 打印出来的number 不是实时更新的number值 */
   console.log(number)
 }
  return log
  /* [] 没有 number */ 
}, [])
 return <div>
  <div onClick={() => newLog()} >打印</div>
  <span onClick={ () => setNumber( number + 1 ) } >增加</span>
 </div>
}
2.1.8 useCallback

useMemo返回cb的运行结果
useCallback返回cb的函数

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

import React, { useState, useCallback } from 'react'
function Button(props) {
 const { handleClick, children } = props;
 console.log('Button -> render');
 return (
   <button onClick={handleClick}>{children}</button>
)
}
const MemoizedButton = React.memo(Button);
export default function Index() {
 const [clickCount, increaseCount] = useState(0);
 const handleClick = () => {
   console.log('handleClick');
   increaseCount(clickCount + 1);
}
 return (
   <div>
     <p>{clickCount}</p>
     <MemoizedButton handleClick=
{handleClick}>Click</MemoizedButton>
   </div>
)
}
// MemoizedButton还是重新渲染了
// Index组件state发⽣变化,导致组件重新渲染;
// 每次渲染导致重新创建内部函数handleClick ,
// 进⽽导致⼦组件Button也重新渲染。
import React, { useState, useCallback } from 'react'
function Button(props) {
 const { handleClick, children } = props;
 console.log('Button -> render');
 return (
   <button onClick={handleClick}>{children}</button>
)
}
const MemoizedButton = React.memo(Button);

export default function Index() {
 const [clickCount, increaseCount] = useState(0);
 // 这⾥使⽤了`useCallback`
 const handleClick = useCallback(() => {
   console.log('handleClick');
   increaseCount(clickCount + 1);
}, [])
 return (
   <div>
     <p>{clickCount}</p>
     <MemoizedButton handleClick=
{handleClick}>Click</MemoizedButton>
   </div>
)
}

2.2 Hooks实战

2.2.1 所有依赖项都必须放在依赖数组中吗?
// 当props.count 和 count 变化时,上报数据
function Demo(props) {
 const [count, setCount] = useState(0);
 const [text, setText] = useState('');
 const [a, setA] = useState('');
 useEffect(() => {
  monitor(props.count, count, text, a);
}, [props.count, count]);
 return (
  <div>
   <button
    onClick={() => setCount(count => count + 1)}
   >
    click
   </button>
   <input value={text} onChange={e => setText(e.target.value)} />
   <input value={a} onChange={e => setA(e.target.value)} />
  </div>
)
}

如果把text 和 a 也引⼊deps中,当text 和 a改变时,也触发了函数执行
Solution:

  1. 不要使用 eslint-plugin-react-hooks 插件,或者可以选择性忽略该插件的警告;
    在这里插入图片描述

  2. 只有⼀种情况,需要把变量放到 deps 数组中,那就是当该变量变化时,需要触发 useEffect 函数
    执⾏。⽽不是因为 useEffect 中⽤到了这个变量!

2.2.2 deps参数不能缓解闭包问题

闭包问题和 useEffect 的 deps 参数没有太⼤关系

// 当进⼊⻚⾯ 3s 后,输出当前最新的 count
// Example 1
function Demo() {
 const [count, setCount] = useState(0);
 useEffect(() => {
  const timer = setTimeout(() => {
   console.log(count)
 }, 3000);
  return () => {
   clearTimeout(timer);
 }
}, [])
 return (
  <button
   onClick={() => setCount(count => count + 1)}
  >
   click
  </button>
)
}
// 输出0
// Example 2
useEffect(() => {
  const timer = setTimeout(() => {
   console.log(count)
 }, 3000);
  return () => {
   clearTimeout(timer);
 }
}, [count])
// 输出1
// Example 3
const [count, setCount] = useState(0);
// 通过 ref 来记录最新的 count
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
 const timer = setTimeout(() => {
  console.log(countRef.current)
}, 3000);
 return () => {
  clearTimeout(timer);
}
}, [])

什么情况下会存在闭包

// 正常情况下,不会存在
const [a, setA] = useState(0);
const [b, setB] = useState(0);
const c = a + b;
useEffect(()=>{
console.log(a, b, c)
}, [a]);
useEffect(()=>{
console.log(a, b, c)
}, [b]);
useEffect(()=>{
console.log(a, b, c)
}, [c]);
// 在延迟调⽤下,会存在闭包
// 1. 使⽤ setTimeout、setInterval、Promise.then 等
// 2. useEffect 的卸载函数
const getUsername = () => {
 return new Promise((resolve, reject) => {
  setTimeout(() => {
   resolve('John');
 }, 3000);
})
}
function Demo() {
 const [count, setCount] = useState(0);
 // setTimeout 会造成闭包问题
 useEffect(() => {
  const timer = setTimeout(() => {
   console.log(count);
 }, 3000);
  return () => {
   clearTimeout(timer);
 }
}, [])
 // setInterval 会造成闭包问题
 useEffect(() => {
  const timer = setInterval(() => {
   console.log(count);
 }, 3000);
  return () => {
   clearInterval(timer);
 }
}, [])
// Promise.then 会造成闭包问题
 useEffect(() => {
  getUsername().then(() => {
   console.log(count);
 });
}, [])
 // useEffect 卸载函数会造成闭包问题
 useEffect(() => {
  return () => {
   console.log(count);
 }
}, []);
 return (
  <button
   onClick={() => setCount(count => count + 1)}
  >
   click
  </button>
)
}
// 都返回0
// 组件初始化,此时 count = 0
// 执⾏ useEffect,此时 useEffect 的函数执⾏,JS 引⽤链记录了对 count=0 的引⽤关// 点击 button,count 变化,但对之前的引⽤已经⽆能为⼒了

Solution:
只有变化时,需要重新执⾏ useEffect 的变量,才要放到 deps 中。⽽不是 useEffect ⽤到的变量都放
到 deps 中。
在有延迟调⽤场景时,可以通过 ref 来解决闭包问题。

2.2.3 尽量不要⽤useCallback
  1. useCallback ⼤部分场景没有提升性能
  2. useCallback让代码可读性变差
Example 1
const someFunc = useCallback(()=> {
 doSomething();
}, []);
return <ExpensiveComponent func={someFunc} />
const ExpensiveComponent = ({ func }) => {
 return (
  <div onClick={func}>
  hello
  </div>
)
}
// 必须⽤React.memo wrapper 住⼦组件,才能避免在参数不变的情况下,不重复渲染
// 所以⼀般项⽬中不建议使⽤useCallback
const ExpensiveComponent = React.memo(({ func }) => {
 return (
  <div onClick={func}>
  hello
  </div>
)
}
// Example 2
const someFuncA = useCallback((d, g, x, y)=> {
 doSomething(a, b, c, d, g, x, y);
}, [a, b, c]);
const someFuncB = useCallback(()=> {
 someFuncA(d, g, x, y);
}, [someFuncA, d, g, x, y]);
useEffect(()=>{
 someFuncB();
}, [someFuncB]);
// 依赖层层传递,最终要找到哪些出发了useEffect执⾏,所以直接引⽤就好
const someFuncA = (d, g, x, y)=> {
 doSomething(a, b, c, d, g, x, y);
};
const someFuncB = ()=> {
 someFuncA(d, g, x, y);
};
useEffect(()=>{
 someFuncB();
}, [...]);
2.2.4 useMemo建议适当使⽤

在deps不变,且⾮简单的基础类型运算的情况下建议使⽤

// 没有使⽤ useMemo
const memoizedValue = computeExpensiveValue(a, b);
// 使⽤ useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// 如果没有使⽤ useMemo,computeExpensiveValue 会在每⼀次渲染的时候执⾏;
// 如果使⽤了 useMemo,只有在 a 和 b 变化时,才会执⾏⼀次
computeExpensiveValue。
const a = 1;
const b = 2;
const c = useMemo(()=> a + b, [a, b]);
const c = a + b; // 内存消耗少
2.2.5 useState的正确使用姿势
  1. 能⽤其他状态计算出来就不⽤单独声明状态。⼀个 state 必须不能通过其它 state/props 直接计算 出来,否则就不⽤定义 state
  2. 保证数据源唯⼀,在项⽬中同⼀个数据,保证只存储在⼀个地⽅
  3. useState 适当合并
// Example 1
const SomeComponent = (props) => {
 const [source, setSource] = useState([
  {type: 'done', value: 1},
  {type: 'doing', value: 2},
])
 const [doneSource, setDoneSource] = useState([])
 const [doingSource, setDoingSource] = useState([])
 useEffect(() => {
  setDoingSource(source.filter(item => item.type === 'doing'))
  setDoneSource(source.filter(item => item.type === 'done'))
}, [source])
 return (
  <div>
   .....
  </div>
)
}
const SomeComponent = (props) => {
 const [source, setSource] = useState([
  {type: 'done', value: 1},
  {type: 'doing', value: 2},
 ])
 const doneSource = useMemo(()=> source.filter(item => item.type ===
'done'), [source]);
 const doingSource = useMemo(()=> source.filter(item => item.type ===
'doing'), [source]);
 return (
  <div>
   .....
  </div>
)
}
// 避免props层层传递,在CR中很难看清楚
// Example 2
function SearchBox({ data }) {
 const [searchKey, setSearchKey] = useState(getQuery('key'));
 const handleSearchChange = e => {
  const key = e.target.value;
  setSearchKey(key);
  history.push(`/movie-list?key=${key}`);
}
return (
   <input
    value={searchKey}
    placeholder="Search..."
    onChange={handleSearchChange}
   />
);
}
function SearchBox({ data }) {
 const searchKey = parse(localtion.search)?.key;
 const handleSearchChange = e => {
  const key = e.target.value;
  history.push(`/movie-list?key=${key}`);
}
 return (
   <input
    value={searchKey}
    placeholder="Search..."
    onChange={handleSearchChange}
   />
);
}
// url params 和 state重复了
// Example 3
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
const [school, setSchool] = useState();
const [age, setAge] = useState();
const [address, setAddress] = useState();
const [weather, setWeather] = useState();
const [room, setRoom] = useState();
const [userInfo, setUserInfo] = useState({
 firstName,
 lastName,
 school,
 age,
 address
});
const [weather, setWeather] = useState();
const [room, setRoom] = useState();
// 更新⼀个时
setUserInfo(s=> ({
 ...s,

2.3 自定义Hooks

注意:⾃定义Hooks本质上还是实现⼀个函数,关键在于实现逻辑
⼀般实现效果如:

const [ a, [b, c...] ] = useXXX(arg1,[arg2, ...]) 
例1: setTitle hook
import { useEffect } from 'react'
const useTitle = (title) => {
  useEffect(() => {
   document.title = title
 }, [])
  return
}
export default useTitle
const App = () => {
  useTitle('new title')
 
  return <div>home</div>
}
例2: updata hook
import { useState } from 'react'
const useUpdate = () => {
  const [, setFlag] = useState()
  const update = () => {
    setFlag(Date.now())
 }
  return update
}
export default useUpdate
// 实际使⽤
const App = (props) => {
 // ...
 const update = useUpdate()
 return <div>
 {Date.now()}
  <div><button onClick={update}>update</button></div>
 </div>
}
例3: useScroll hook
import { useState, useEffect } from 'react'
const useScroll = (scrollRef) => {
 const [pos, setPos] = useState([0,0])
 useEffect(() => {
  function handleScroll(e){
   setPos([scrollRef.current.scrollLeft, scrollRef.current.scrollTop])
 }
  scrollRef.current.addEventListener('scroll', handleScroll, false)
  return () => {
   scrollRef.current.removeEventListener('scroll', handleScroll,
false)
 }
}, [])
 return pos
}
export default useScroll
// ⽤法
import React, { useRef } from 'react'
import { useScroll } from 'hooks'
const Home = (props) => {
 const scrollRef = useRef(null)
 const [x, y] = useScroll(scrollRef)
 return <div>
   <div ref={scrollRef}>
    <div className="innerBox"></div>
   </div>
   <div>{ x }, { y }</div>
  </div>
}
2.3.1 防抖hook
import { useEffect, useRef } from 'react'
const useDebounce = (fn, ms = 30, deps = []) => {
  let timeout = useRef()
  useEffect(() => {
    if (timeout.current) clearTimeout(timeout.current)
    timeout.current = setTimeout(() => {
      fn()
   }, ms)
 }, deps)
  const cancel = () => {
    clearTimeout(timeout.current)
    timeout = null
 }
  return [cancel]
}
export default useDebounce
// 实际使⽤
import { useDebounce } from 'hooks'
const Home = (props) => {
 const [a, setA] = useState(0)
 const [b, setB] = useState(0)
 const [cancel] = useDebounce(() => {
  setB(a)
}, 2000, [a])
 const changeIpt = (e) => {
  setA(e.target.value)
}
 return <div>
  <input type="text" onChange={changeIpt} />
 { b } { a }
 </div>
}
2.3.2 节流hook
import { useEffect, useRef, useState } from 'react'
const useThrottle = (fn, ms = 30, deps = []) => {
  let previous = useRef(0)
  let [time, setTime] = useState(ms)
  useEffect(() => {
    let now = Date.now();
    if (now - previous.current > time) {
      fn();
      previous.current = now;
   }
 }, deps)
  const cancel = () => {
    setTime(0)
 }
  return [cancel]
}
export default useThrottle

3.异步组件

随着项⽬的增⻓,代码包也会随之增⻓,尤其是在引⼊第三⽅的库的情况下,要避免因体积过⼤导致加
载时间过⻓。
React16.6中,引⼊了 React.lazy 和 React.Suspense 两个API,再配合动态 import() 语法就可以实现
组件代码打包分割和异步加载。

传统模式:渲染组件-> 请求数据 -> 再渲染组件
异步模式:请求数据-> 渲染组件;

// demo
import React, { lazy, Suspense } from 'react';
// lazy 和 Suspense 配套使⽤,react原⽣⽀持代码分割
const About = lazy(() => import(/* webpackChunkName: "about"
*/'./About'));
class App extends React.Component {
  render() {
    return (
      <div className="App">
        <h1>App</h1>
        <Suspense fallback={<div>loading</div>}>
          <About />
        </Suspense>
      </div>
   );
 }
}
export default App;

3.1 手写异步组件

Suspense组件需要等待异步组件加载完成再渲染异步组件的内容。

  1. lazy wrapper住异步组件,React第⼀次加载组件的时候,异步组件会发起请求,并且抛出异常,终
    ⽌渲染;
  2. Suspense⾥有componentDidCatch⽣命周期函数,异步组件抛出异常会触发这个函数,然后改变
    状态使其渲染fallback参数传⼊的组件;
  3. 异步组件的请求成功返回之后,Suspense组件再次改变状态使其渲染正常⼦组件(即异步组件);
/ comp About
const About = lazy(() => new Promise(resolve => {
  setTimeout(() => {
    resolve({
      default: <div>component content</div>
   })
 }, 1000)
}))
// comp Suspense
import React from 'react'
class Suspense extends React.PureComponent {
  /**
  * isRender 异步组件是否就绪,可以渲染
  */
  state = {
    isRender: true
 }
  componentDidCatch(e) {
    this.setState({ isRender: false })
    e.promise.then(() => {
      /* 数据请求后,渲染真实组件 */
      this.setState({ isRender: true })
   })
 }
  render() {
    const { fallback, children } = this.props
    const { isRender } = this.state
    return isRender ? children : fallback
 }
}
export default Suspense
// comp lazy
import React, { useEffect } from 'react'
export function lazy(fn) {
  const fetcher = {
    status: 'pending',
    result: null,
    promise: null,
 }
  return function MyComponent() {
    const getDataPromise = fn()
    fetcher.promise = getDataPromise
    getDataPromise.then(res => {
    fetcher.status = 'resolved'
      fetcher.result = res.default
   })
    useEffect(() => {
      if (fetcher.status === 'pending') {
        throw fetcher
     }
   }, [])
    if (fetcher.status === 'resolved') {
      return fetcher.result
   }
    return null
 }
}
// 实现的效果与React⽀持内容保持⼀致
import React, {Suspese, lazy} from 'react'
const About= lazy(() => { import('../About') });
class App extends React.Component {
 render() {
  /**
  * 1. 使⽤ React.Lazy 和 import() 来引⼊组件
  * 2. 使⽤<React.Suspense></React.Suspense>来做异步组件的⽗组件,并使⽤
fallback 来实现组件未加载完成时展示信息
  * 3. fallback 可以传⼊html,也可以⾃⾏封装⼀个统⼀的提示组件
  */
  return (
   <div>
    <Suspense
     fallback={
      <Loading />
    }
    >
     <About />
    </Suspense>
   </div>
 )
}
}
export default ReactComp;

4.React18新特性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值