开发记录更新中...

170 阅读5分钟

记录一下

一.判断对象为空的方法

1.利用JSON.stringify()方法强制转换为字符串

let a = {};
JSON.stringify(a) == '{}'//true

2.利用Object.getOwnPropertyNames方法来判断

let a = {};
Object.getOwnPropertyNames(a).length == 0

3.利用ES6的Object.keys()来判断

let a = {};
let arr = Object.keys(a);
arr.length == 0; //true

二.当执行正则表达式时

const regx = new RegExp(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/g);
regx.test(4.4.3.2); // true
regx.test(4.4.3.2);//false

当全局执行完第一次匹配之后,会停留在当前匹配的位置,所以第二次重复执行的时候就返回false,如果非全局执行则不会出现这种情况。

也可以设置

regx.lastIndex = 0;
regx.test('4.4.3.2') //true

三.css3transform能够避免浏览器的重排和回流?

ref

1.保存不变的实例或者属性

useRef在函数组件中使用,当拿不到最新的值时,可以将最新的值挂在 ref 上来保证这些 hook 在回调中拿到的都是最新的值,同时避免不必要的重新渲染。因为useRef返回的Ref对象在组件的整个声明周期内保持不变

比如在防抖函数中,将传入的函数挂载在ref上

const UseDebounce = (fn: Function, delay: any, dep: []) => {
  const { current } = useRef({ fn, timer: null });
  useEffect(() => {
    current.fn = fn;
  }, [fn]);
  return useCallback((...args) => {
    if (current.timer) {
      // @ts-ignore
      clearTimeout(current.timer);
    }
    // @ts-ignore
    current.timer = setTimeout(() => {
      current.fn(...args);
    }, delay);
  }, dep);
};

2.传递ref

Ref 转发是一项将 ref 自动地通过组件传递到子组件的技巧,其允许某些组件接收ref,并将其向下传递给子组件,函数组件中使用forwardRef是为了在函数组件中传递ref,而class组件中自带ref进行传递,而函数组件中不自带,在使用forwardRef后,可以多接受一个ref参数,正常使用情况:

const AntdSearch = forwardRef((props,ref) => {
    React.useImperativeHandle(ref, () => ({
    // name:'hhhhh',
    //获取选中项
    
    //获取数据
    reset: () => {
      handleRedo()
      // return [...prop.dataSource];
    },
    //重新加载
  }));
})

此时,父组件可以拿到挂载到子组件Dom上的节点,而想获取子组件的属性和方法,则需要配合useImperativeHandle 来获取。在使用forwardRef暴露子组件实例的时候,如遇到该组件已经被高阶组件包裹的时候,此时拿到的是最外层的容器组件即高阶组件的实例,则需要修改为:

const Search = (props) => {
    let {refInstance} = props;
    React.useImperativeHandle(refInstance, () => ({
    // name:'hhhhh',
    //获取选中项
    
    //获取数据
    reset: () => {
      handleRedo()
      // return [...prop.dataSource];
    },
    // getVal:() => {
    //   // console.log(form.getFieldsValue())
    //   return form.getFieldsValue();
    // },
    getFormInstance:() => {
      return form
    }
    //重新加载
  }));
}
const AntdSearch = functionHoc(Search)
//@ts-ignore
export default forwardRef((props,ref) => <AntdSearch locale={props?.locale} refInstance={ref} {...props} />);

不能对已经卸载的组件执行状态更新

问题描述:

image-20220329164810506

//语言设置
  const loadLocales = () => {
    // console.log('loadLocales',locale)
    let currentLocale = intl.determineLocale({
      localStorageLocaleKey:"locale"
    })
    intl.init({
      currentLocale,
      locales
    }).then(()=> {
      if(!mountedRef.current){
        return;
      }
      setInitDone(true);
      // console.log('currentLocale')
    })
  }
  useEffect(() => {
    // console.log('loadlocale')
      mountedRef.current = true;
      loadLocales();
      return () => {
        mountedRef.current = false;
      }
  },[]);
let isMountedRef = useRef(false)
useEffect(() => {
  isMountedRef.current = true
  return () => {
    isMountedRef.current = false
  }
}, [])
​
async function handleSubmit() {
  setPending(true)
  await post('/someapi')
  if (!isMountedRef.current) {
    setPending(false)
  }
}

对象序列化

出现的问题:当JSON.stringify遇到对象值为undefined时,会忽略其值并跳过该值的序列化。

  1. 序列化与反序列化:在OSI七层协议模型中展现层(Presentation Layer)的主要功能是把应用层的对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层的对象 -- 这两个功能就是序列化和反序列化。
  2. JSON.stringify(value[, replacer [, space]]):用来将一个 JavaScript 值(对象或者数组)转换为一个 JSON 字符串,如果指定了 replacer 是一个函数,则可以选择性地替换值,或者如果指定了 replacer 是一个数组,则可选择性地仅包含数组指定的属性。
  3. 当undefined、任意的函数、正则以及 symbol 作为对象属性值时 JSON.stringify() 对跳过(忽略)它们进行序列化
const data = {
  a: "aaa",
  b: undefined,
  c: Symbol("dd"),
  fn: function() {
    return true;
  }
};
JSON.stringify(data); // 输出:?// "{"a":"aaa"}"

4.当undefined、任意的函数、正则以及 symbol 作为数组元素值时,JSON.stringify() 将会将它们序列化为 null

JSON.stringify(["aaa", undefined, function aa() {
    return true
  }, Symbol('dd')])  // 输出:?// "["aaa",null,null,null]"

5.当undefined、任意的函数、正则以及 symbol 被 JSON.stringify() 作为单独的值进行序列化时,都会返回 undefined

JSON.stringify(function a (){console.log('a')})
// undefined
JSON.stringify(undefined)
// undefined
JSON.stringify(Symbol('dd'))
// undefined

6.转换值如果有 toJSON() 函数,该函数返回什么值,序列化结果就是什么值,并且忽略其他属性的值

JSON.stringify({
    say: "hello JSON.stringify",
    toJSON: function() {
      return "today i learn";
    }
  })
// "today i learn"

7.NaN和Infinity格式的数值及null都会被当做null。

JSON.stringify(NaN)
// "null"
JSON.stringify(null)
// "null"
JSON.stringify(Infinity)
// "null"

8.基本数据类型布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。

JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// "[1,"false",false]"

9.可以实现深拷贝:JSON.parse(JSON.stringify()),

造成的问题:

obj_after =JSON.parse(JSON.stringify(obj))会导致 obj_after.proto.constructor 指向发生变化,统一指向 Object,而不是和obj的一致(不够优雅),

如果对象中有undefined,function,symbol和正则的时候,不能完全深拷贝,

当对象中包含循环引用的时候,会直接抛出错误

// 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。 
const obj = {
  name: "loopObj"
};
const loopObj = {
  obj
};
// 对象之间形成循环引用,形成闭环
obj.loopObj = loopObj;

// 封装一个深拷贝的函数
function deepClone(obj) {
  return JSON.parse(JSON.stringify(obj));
}
// 执行深拷贝,抛出错误
deepClone(obj)
/**
 VM44:9 Uncaught TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    |     property 'loopObj' -> object with constructor 'Object'
    --- property 'obj' closes the circle
    at JSON.stringify (<anonymous>)
    at deepClone (<anonymous>:9:26)
    at <anonymous>:11:13
 */

for循环的不当使用

问题:通过这个方法,只能拿到数组的第一项的值,然后一直报错,

useEffect(() => {
        getDataList({});
        let val = 'id=43&type=monitor&code=824b74e0eb9699f3fbae4d29b135808a&state=1'.split('&');
        console.log(text(val));
    },[]);
    const text = (val:any) => {
        for (let i = 0, len = val.length; i < len; i++) {
            let pair = val[i].split('=');
            if (pair[0] === 'code') {
              return pair[1];
            }
            return null;
          }
    }

1.for方法和for of方法可以使用break或者continue跳过或中断,for ...of直接访问的是实际元素,for遍历数组索引

const list = [1, 2, 3, 4, 5, 6, 7, 8,, 10, 11];

for (let i = 0, len = list.length; i < len; i++) {
  if (list[i] === 5) {
    break; // 1 2 3 4
    // continue; // 1 2 3 4 6 7 8 undefined 10 11
  }
  console.log(list[i]);
}

for (const item of list) {
  if (item === 5) {
    break; // 1 2 3 4
    // continue; // 1 2 3 4 6 7 8 undefined 10 11
  }
  console.log(item);
}

2.forEach方法用于调用数组的每个元素,并将元素传递给回调函数,数组中的每个值都会调用回调函数,并且无法跳出循环且没有返回值。

array.forEach(function(currentVal,index,arr),thisVal);

currentVal:必需,当前元素

index:可选,当前元素索引值

arr:可选,当前元素所属的数组对象

let arr = [1,2,3,4,5];
arr.forEach((item,index,arr)=>{
	console.log(index+':'+item)
})

该方法还可以有第二个参数,用来绑定回调函数内部this变量(前提是回调函数不能是箭头函数,因为箭头函数没有this)

let arr = [1,2,3,4,5];
let arr1 = [2,4,6,8,10];
arr.forEach(function(item,index,arr){
	console.log(this[index]);// 2,4,6,8,10
},arr1)

3.map方法生成一个新数组,都不会改变原数组,跳过空元素,将回调函数的返回值组成一个新数组,数组长度与原数组一致,无法跳出循环,有返回值

const newList = list.map(item => {
  console.log(item);
  return item.id;
});
// newList: [1, empty, 2, 3]

list: [
   { name: '头部导航', type: 'nav', id: 1 },
   empty,
   { name: '轮播', type: 'content', id: 2 },
   { name: '页脚', type: 'nav', id: 3 },
 ]
list.forEach((item: any) => {
    return (
        <div
        className="content-item"
        key={item.id}
        draggable="true"
        onDragStart={() => handleDragStart(item)}
        >
        <span style={{ marginRight: 10 }}>{item.label}</span>
        </div>
    );
 })}
 
 页面无任何显示,因为forEach没有返回值

4.some和every,二者都是用来做数组条件判断的,都是返回一个布尔值,some若某一元素满足条件,返回 true,every 与 some 相反,若所有元素不满足条件,返回 false

const list = [
  { name: '头部导航', backward: false },
  { name: '轮播', backward: true },
  { name: '页脚', backward: false },
];
const someBackward = list.some(item => item.backward);
// someBackward: true
const everyNewest = list.every(item => !item.backward);
// everyNewest: false

5.性能比较

var list = Array(100000).fill(1)

console.time('for');
for (let index = 0, len = list.length; index < len; index++) {
}
console.timeEnd('for');
// for: 2.427642822265625 ms

console.time('every');
list.every(() => { return true })
console.timeEnd('every')
// some: 2.751708984375 ms

console.time('some');
list.some(() => { return false })
console.timeEnd('some')
// some: 2.786590576171875 ms

console.time('foreach');
list.forEach(() => {})
console.timeEnd('foreach');
// foreach: 3.126708984375 ms

console.time('map');
list.map(() => {})
console.timeEnd('map');
// map: 3.743743896484375 ms

console.time('forof');
for (let index of list) {
}
console.timeEnd('forof')
// forof: 6.33380126953125 ms
是否可终止是否跳出本次循环
***breakcontinuereturn性能(ms)
for可以可以可以 跳出循环2.42
forEach不可以不可以不可以3.12
map不可以不可以可以 不跳出循环3.74
for of可以可以可以 跳出循环6.33
some不可以不可以可以,返回Boolean2.78
every不可以不可以可以,返回Boolean2.75

高阶组件的使用

在table组件的封装中,使用高阶组件来控制组件的渲染,因此也打开了了解高阶组件的神秘大门

import React, { useState, useEffect, useRef } from 'react';
//根据locale语言切换控制组件的刷新
function functionHoc (InnerComp) {
    // console.log(InnerComp);
    return function Index({...props}){
        // console.log(props,props.locale)
        const [forceUpdate,setForceUpdate] = useState(true);
        const mountRef = useRef(null);
        let timer = null;
        useEffect(() => {
            // console.log(locale)
            mountRef.current = true
            if(props.locale){
                // console.log('return')
                Promise.resolve(() => {
                    // console.log('promise');
                    return 'success'
                    
                }).then(() => {
                    // console.log('then')
                    if(!mountRef.current){
                        return;
                    }
                    setForceUpdate(false);
                })
                clearTimeout(timer);
                // @ts-ignore
                timer = setTimeout(() => {
                    setForceUpdate(true);
                },1);
                
            }
            return () => {
                mountRef.current = false
                timer = null;
                clearTimeout(timer);
            }
        },[props.locale])
        return forceUpdate?<InnerComp {...props}/>:null
    }
}
export default functionHoc;

高阶组件就是将组件转换成另一个组件,而经过包装过后的组件获得了强化,节省了逻辑,解决了原有组件的缺陷,这是高阶组件及其意义。高阶组件的作用是强化组件、复用逻辑、提升渲染性能等作用,输入一个组件,输出一个组件。

1.两种不同的使用高阶组件的方式:装饰器模式和函数式模式

@withRouter
@keepaliveLifeCycle
class Index extends React.Componen{
    /* ... */
}

function Index(){
    /* .... */
}
export default withRouter( keepaliveLifeCycle(Index) )) 

2.两种不同的高阶组件

1)正向代理

就是用组件包裹一层代理组件,在代理组件上,我们可以做一些,对源组件的代理操作。在fiber tree上,先mounted代理组件,然后才是我们的业务组件。我们可以理解为父子组件关系,父组件对子组件进行一系列强化操作。

function HOC(WrapComponent){
    return class Advance extends React.Component{
       state={
           name:'alien'
       }
       render(){
           return <WrapComponent  { ...this.props } { ...this.state }  />
       }
    }
}

优点:

① 正常属性代理可以和业务组件低耦合,零耦合,对于条件渲染和props属性增强,只负责控制子组件渲染和传递额外的props就可以,所以无须知道,业务组件做了些什么。所以正向属性代理,更适合做一些开源项目的hoc,目前开源的HOC基本都是通过这个模式实现的。

② 同样适用于class声明组件,和function声明的组件。

③ 可以完全隔离业务组件的渲染,相比反向继承,属性代理这种模式。可以完全控制业务组件渲染与否,可以避免反向继承带来一些副作用,比如生命周期的执行。

④ 可以嵌套使用,多个hoc是可以嵌套使用的,而且一般不会限制包装HOC的先后顺序。

缺点:

① 一般无法直接获取业务组件的状态,如果想要获取,需要ref获取组件实例。

② 无法直接继承静态属性。如果需要继承需要手动处理,或者引入第三方库。

2) 反向继承

反向继承和属性代理有一定的区别,在于包装后的组件继承了业务组件本身,所以我们无须在去实例化我们的业务组件。当前高阶组件就是继承后,加强型的业务组件。这种方式类似于组件的强化,所以你没必要知道当前的业务组件

class Index extends React.Component{
  render(){
    return <div> hello,world  </div>
  }
}
function HOC(Component){
    return class wrapComponent extends Component{ /* 直接继承需要包装的组件 */

    }
}
export default HOC(Index)

优点:

① 方便获取组件内部状态,比如stateprops ,生命周期,绑定的事件函数等

② es6继承可以良好继承静态属性。我们无须对静态属性和方法进行额外的处理

缺点:

① 无状态组件无法使用。

② 和被包装的组件强耦合,需要知道被包装的组件的内部状态,具体是做什么?

③ 如果多个反向继承hoc嵌套在一起,当前状态会覆盖上一个状态。这样带来的隐患是非常大的,比如说有多个componentDidMount,当前componentDidMount会覆盖上一个componentDidMount。这样副作用串联起来,影响很大。

3.高阶组件的作用:

1).逻辑复用:高阶组件更像是一个加工react组件的工厂,批量对原有组件进行加工包装处理。我们可以根据业务需求定制化专属的HOC,这样可以解决复用逻辑。

2).强化props:这个是HOC最常用的用法之一,高阶组件返回的组件,可以劫持上一层传过来的props,然后混入新的props,来增强组件的功能。代表作react-router中的withRouter

function classHOC(WrapComponent){
  return class  Idex extends React.Component{
      constructor(){
        super()
        this.state={
          name:'alien'
        }
      }
      changeName(name){
        this.setState({ name })
      }
      render(){
          return <WrapComponent 
          { ...this.props }  
          { ...this.state } 
          changeName={this.changeName.bind(this)} 
          />
      }
  }
}
function Index(props){
  const [ value ,setValue ] = useState(null)
  const { name ,changeName } = props
  return <div>
    <div>   hello,world , my name is { name }</div>
    改变name <input onChange={ (e)=> setValue(e.target.value)  }  />
    <button onClick={ ()=>  changeName(value) }  >确定</button>
  </div>
}

export default classHOC(Index)
import React from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";
import invariant from "tiny-invariant";

import RouterContext from "./RouterContext.js";

/**
 * A public higher-order component to access the imperative API
 */
function withRouter(Component) {
  const displayName = `withRouter(${Component.displayName || Component.name})`;
  const C = props => {
    // 如果想要设置被 withRouter 包裹的组件的 ref,使用 wrappedComponentRef
    const { wrappedComponentRef, ...remainingProps } = props;

    return (
      <RouterContext.Consumer>
        {context => {
          invariant(
            context,
            `You should not use <${displayName} /> outside a <Router>`
          );

          // 将 context 加入到 Component 中
          return (
            <Component
              {...remainingProps}
              {...context}
              ref={wrappedComponentRef}
            />
          );
        }}
      </RouterContext.Consumer>
    );
  };

  C.displayName = displayName;
  C.WrappedComponent = Component;

  // 当你给一个组件添加一个HOC时,原来的组件会被一个container的组件包裹。这意味着新的组件不会有原来组件任何静态方法。
  // 为了解决这个问题,可以在return container之前将 static方法copy到container上面
  // 用 hoist-non-react-statics来自动复制所有non-React的static methods
  return hoistStatics(C, Component);
}

export default withRouter;

3).赋能组件:HOC有一项独特的特性,就是可以给被HOC包裹的业务组件,提供一些拓展功能,比如说额外的生命周期,额外的事件,但是这种HOC,可能需要和业务组件紧密结合。典型案例react-keepalive-router中的keepaliveLifeCycle就是通过HOC方式,给业务组件增加了额外的生命周期。

import {lifeCycles} from '../core/keeper'
import hoistNonReactStatic from 'hoist-non-react-statics'
function keepaliveLifeCycle(Component) {
   class Hoc extends React.Component {
    cur = null
    handerLifeCycle = type => {
      if (!this.cur) return
      const lifeCycleFunc = this.cur[type]
      isFuntion(lifeCycleFunc) && lifeCycleFunc.call(this.cur)
    }
    componentDidMount() { 
      const {cacheId} = this.props
      cacheId && (lifeCycles[cacheId] = this.handerLifeCycle)
    }
    componentWillUnmount() {
      const {cacheId} = this.props
      delete lifeCycles[cacheId]
    }
     render=() => <Component {...this.props} ref={cur => (this.cur = cur)}/>
  }
  return hoistNonReactStatic(Hoc,Component)
}

keepaliveLifeCycle 的原理就是通过ref或获取 class 组件的实例,在hoc初始化时候进行生命周期的绑定, 在 hoc 销毁阶段,对生命周期进行解绑, 然后交给keeper统一调度,keeper通过调用实例下面的生命周期函数,来实现缓存生命周期功能的。

4).控制渲染:劫持渲染是HOC一个特性,在wrapComponent包装组件中,可以对原来的组件,进行条件渲染,节流渲染,懒加载等功能,典型代表做react-redux中connect和 dva中 dynamic 组件懒加载。

function renderHOC(WrapComponent){
  return class Index  extends React.Component{
      constructor(props){
        super(props)
        this.state={ visible:true }  
      }
      setVisible(){
         this.setState({ visible:!this.state.visible })
      }
      render(){
         const {  visible } = this.state 
         return <div className="box"  >
           <button onClick={ this.setVisible.bind(this) }>挂载组件</button>
           {visible?<WrapComponent{ ...this.props } 
           setVisible={ this.setVisible.bind(this) }   
           />  
           : 
           <div className="icon" ><SyncOutlined spin  className="theicon"  /></div> }
         </div>
      }
  }
}

class Index extends React.Component{
  render(){
    const { setVisible } = this.props
    return <div className="box" >
        <p>hello,my name is alien</p>
        <img src='https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=294206908,2427609994&fm=26&gp=0.jpg'   
        /> 
        <button onClick={() => setVisible()}  > 卸载当前组件 </button>
    </div>
  }
}
export default renderHOC(Index)
import store from './redux/store'
import { ReactReduxContext } from './Context'
import { useContext } from 'react'
function connect(mapStateToProps){
   /* 第一层: 接收订阅state函数 */
    return function wrapWithConnect (WrappedComponent){
        /* 第二层:接收原始组件 */
        function ConnectFunction(props){
            const [ , forceUpdate ] = useState(0)
            const { reactReduxForwardedRef ,...wrapperProps } = props
            
            /* 取出Context */
            const { store } = useContext(ReactReduxContext)

            /* 强化props:合并 store state 和 props  */
            const trueComponentProps = useMemo(()=>{
                  /* 只有props或者订阅的state变化,才返回合并后的props */
                 return selectorFactory(mapStateToProps(store.getState()),wrapperProps) 
            },[ store , wrapperProps ])

            /* 只有 trueComponentProps 改变时候,更新组件。  */
            const renderedWrappedComponent = useMemo(
              () => (
                <WrappedComponent
                  {...trueComponentProps}
                  ref={reactReduxForwardedRef}
                />
              ),
              [reactReduxForwardedRef, WrappedComponent, trueComponentProps]
            )
            useEffect(()=>{
              /* 订阅更新 */
               const checkUpdate = () => forceUpdate(new Date().getTime())
               store.subscribe( checkUpdate )
            },[ store ])
            return renderedWrappedComponent
        }
        /* React.memo 包裹  */
        const Connect = React.memo(ConnectFunction)

        /* 处理hoc,获取ref问题 */  
        if(forwardRef){
          const forwarded = React.forwardRef(function forwardConnectRef( props,ref) {
            return <Connect {...props} reactReduxForwardedRef={ref} />
          })
          return hoistStatics(forwarded, WrappedComponent)
        } 
        /* 继承静态属性 */
        return hoistStatics(Connect,WrappedComponent)
    } 
}
export default Index

connect 涉及到的功能点还真不少,首先第一层接受订阅函数,第二层接收原始组件,然后用forwardRef处理ref,用hoistStatics 处理静态属性的继承,在包装组件内部,合并props,useMemo缓存原始组件,只有合并后的props发生变化,才更新组件,然后在useEffect内部通过store.subscribe()订阅更新。这里省略了Subscription概念,真正的connect中有一个Subscription专门负责订阅消息

异步组件:dva里面的dynamic就是应用的HOC模式实现的组件异步加载

export default function AsyncRouter(loadRouter) {
  return class Content extends React.Component {
    state = {Component: null}
    componentDidMount() {
      if (this.state.Component) return
      loadRouter()
        .then(module => module.default)
        .then(Component => this.setState({Component},
         ))
    }
    render() {
      const {Component} = this.state
      return Component ? <Component {
      ...this.props
      }
      /> : null
    }
  }
}
const Index = AsyncRouter(()=>import('../pages/index'))

require与import

require是在脚本执行中调用,在代码的任何地方,是执行赋值是模块内部变量值的拷贝,从exports属性上去取导出的值,import是在预编译阶段执行的,当遇到import,会生成一个只读引用,等到脚本真正执行时,再根据这个只读引用到被加载模块中取值,当原始值变化时,import的值也会跟着变化,不会缓存值

  1. 导入require 导出 exports/module.exports 是 CommonJS 的标准,通常适用范围如 Node.js
  2. import/export 是 ES6 的标准,通常适用范围如 React
  3. require 是赋值过程并且是运行时才执行,也就是同步加载
  4. require 可以理解为一个全局方法,因为它是一个方法所以意味着可以在任何地方执行。
  5. import 是解构过程并且是编译时执行, 它会生成外部模块的引用而不是加载模块, 等到真正使用到该模块的时候才会去加载模块中的值,理解为异步加载
  6. import 会提升到整个模块的头部,具有置顶性,但是建议写在文件的顶部
  7. import表达式可以模拟require的导入,并返回一个promise;

webpack的Babel会将es6语法转换成es5语法,会将es6的import和export default转换成commonjs规范

export default 123;
export const a = 123;
const b = 3;
const c = 4;
export { b, c };

import a from './a.js';



exports.default = 123;
exports.a = 123;
exports.b = 3;
exports.c = 4;
exports.__esModule = true;

var a = require(./a.js)

我们在使用各大 UI 组件库时都会被介绍到为了避免引入全部文件,请使用 babel-plugin-component 等babel 插件。

import { Button, Select } from 'antd'

var a = require('antd');
var Button = a.Button;
var Select = a.Select;


import Button from 'antd/lib/button'
import Select from 'antd/lib/select'

tree-shaking

减少无用的代码,减少包的体积,比如检查变量的引用,如果没有该引用,会直接剔除,有些深一点的无法判断,需要使用插件的方式来剔除,比如:webpack-deep-scope-analysis-plugin

useMemo,useCallback,useEffect依赖尽量使用基本类型

lazy loading懒加载和bundle/code splitting代码分割

包体积越小,app越快,使用source-map-explore或者@next/bundle-analyzes进行包体积分析

推荐使用react-hook-forms表单库