react使用中的细节

react 中顺序加载script 标签

export default class Script extends React.Component {

  static defaultProps = {
    attributes: {},
    onCreate: () => {},
    onError: () => {},
    onLoad: () => {},
  }
  static scriptObservers = {};
  // 特定的URL是否已经加载完成
  // this.constructor.scriptObservers[url][this.scriptLoaderId] = this.props;
  // 每一个URL对应于多个scriptLoaderId,但是只会检查一个是否已经加载完毕
  static loadedScripts = {};
  // this.constructor.loadedScripts[url] = true;
  static erroredScripts = {};
  // this.constructor.erroredScripts[url] = true;
  static idCount = 0;
  // 该组件已经被实例化了多少个对象
  constructor(props) {
    super(props);
    this.scriptLoaderId = `id${this.constructor.idCount++}`; 
    //1.如果某一个页面有多个该Script标签,那么其特定的this.scriptLoaderId都是唯一的
  }
  componentDidMount() {
    const { onError, onLoad, url } = this.props;
    //fix 1:如果该URL已经加载过了,然后又在页面其他地方要求加载,因为this.constructor.loadedScripts[url]已经被设置为true,那么直接调用onLoad方法
    if (this.constructor.loadedScripts[url]) {
      onLoad();
      return;
    }
    //fix 2:如果该URL已经加载过了,而且加载出错,然后又在页面其他地方要求加载,因为tthis.constructor.erroredScripts[url]已经被设置为true,那么直接调用onError方法
    if (this.constructor.erroredScripts[url]) {
      onError();
      return;
    }
    // If the script is loading, add the component to the script's observers
    // and return. Otherwise, initialize the script's observers with the component
    // and start loading the script.
    // fix 3:如果某一个URL已经在加载了,即this.constructor.scriptObservers[url]被设置为特定的值了,那么如果还要求该URL那么直接返回,防止一个组件被加载多次
    if (this.constructor.scriptObservers[url]) {
      this.constructor.scriptObservers[url][this.scriptLoaderId] = this.props;
      return;
    }
    //8.this.constructor.scriptObservers用于注册某一个URL特定的对象,其值为为该组件添加的所有的props对象,而key为该组件实例的this.scriptLoaderId
    this.constructor.scriptObservers[url] = {
      [this.scriptLoaderId]: this.props
    };
    this.createScript();
  }
  componentWillUnmount() {
    const { url } = this.props;
    const observers = this.constructor.scriptObservers[url];
    // If the component is waiting for the script to load, remove the
    // component from the script's observers before unmounting the component.
    // componentWillUnmount只是卸载当前的组件实例而已,所以直接delete当前实例的this.scriptLoaderId
    if (observers) {
      delete observers[this.scriptLoaderId];
    }
  }

  createScript() {
    const { onCreate, url, attributes } = this.props;
    //1.onCreate在script标签创建后被调用
    const script = document.createElement('script');
    onCreate();
    // add 'data-' or non standard attributes to the script tag
    // 2.所有attributes指定的属性都会被添加到script标签中
    if (attributes) {
      Object.keys(attributes).forEach(prop => script.setAttribute(prop, attributes[prop]));
    }
    script.src = url;
    // default async to true if not set with custom attributes
    // 3.如果script标签没有async属性,表示不是异步加载的
    if (!script.hasAttribute('async')) {
      script.async = 1;
    }
    //5.shouldRemoveObserver(observers[key])用于移除特定的监听器并触发onLoad
    const callObserverFuncAndRemoveObserver = (shouldRemoveObserver) => {
      const observers = this.constructor.scriptObservers[url];
      //监听当前URL的scriptObservers,然后获取该Observer的key,即对应于this.scriptLoaderId,每一个组件实例都是唯一的,一个URL可能多个this.scriptLoadedId相对应:
      // if (this.constructor.scriptObservers[url]) {
    //   this.constructor.scriptObservers[url][this.scriptLoaderId] = this.props;
    //   return;
    // }
      Object.keys(observers).forEach((key) => {
        //如果某一个特定的key对应的,传入的observers[key]就是该组件实例的this.props
        if (shouldRemoveObserver(observers[key])) {
          delete this.constructor.scriptObservers[url][this.scriptLoaderId];
        }
      });
    };
    //4.onload将该URL已经加载的状态设置为true
    script.onload = () => {
      this.constructor.loadedScripts[url] = true;
      callObserverFuncAndRemoveObserver((observer) => {
        //6.调用用户自己的onLoad表示脚本加载完成
        observer.onLoad();
        return true;
      });
    }
    script.onerror = () => {
      this.constructor.erroredScripts[url] = true;
      callObserverFuncAndRemoveObserver((observer) => {
        //7.调用用户自己的onError表示加载错误
        observer.onError();
        return true;
      });
    };
    document.body.appendChild(script);
  }
  render() {
    return null;
  }
}









handleScriptLoad = value => {
  ++this.scriptLoaderCount;
  //两个js脚本
 if (this.scriptLoaderCount == 2) {
  this.map = new AMap.Map("my__amp--container", {
    resizeEnable: true,
    zoom: 13,
    center: [116.39, 39.9]
  });
  window.AMap.plugin("AMap.Geocoder", () => {
    this.geocoder = new AMap.Geocoder({
      //city: "010" //城市,默认:“全国”
    });
    this.marker = new AMap.Marker({
      map: this.map,
      bubble: true
    });
  });
render(){
  return <div>
 <Script
    url=" https://webapi.amap.com/maps?v=1.4.2&key=eafedbd654c4c2996d778d04f3cba020"
      onLoad={this.handleScriptLoad}
  />
  <Script
    url="https://webapi.amap.com/demos/js/liteToolbar.js"
    onLoad={this.handleScriptLoad}
  />
  </div>
}

比如有一次在页面中接入高德地图,需要保证当其依赖的js都加载完毕以后才渲染地图,所以有如下的方法:

  //两个js脚本
 if (this.scriptLoaderCount == 2) {
  this.map = new AMap.Map("my__amp--container", {
    resizeEnable: true,
    zoom: 13,
    center: [116.39, 39.9]
  });
  window.AMap.plugin("AMap.Geocoder", () => {
    this.geocoder = new AMap.Geocoder({
      //city: "010" //城市,默认:“全国”
    });
    this.marker = new AMap.Marker({
      map: this.map,
      bubble: true
    });
  });
render(){
  return <div>
 <Script
    url=" https://webapi.amap.com/maps?v=1.4.2&key=eafedbd654c4c2996d778d04f3cba020"
      onLoad={this.handleScriptLoad}
  />
  <Script
    url="https://webapi.amap.com/demos/js/liteToolbar.js"
    onLoad={this.handleScriptLoad}
  />
  </div>
}

生命周期调用顺

import React from 'react';
export default class Parent extends React.Component {

  state = {
    count: 0
  };
  /**
    * (1)componentWillReceiveProps签名知道只有当组件的props发生改变后才会调用该方法
    * (2)componentWillReceiveProps在shouldComponentUpdate之前调用的
    */
  componentWillReceiveProps(nextProps) {
    console.log("Parent的nextProps为", nextProps);
  }
  /**
    * (1)组件state或者props发生改变都会触发这个方法,但是如果组件没有调用setState那么不会调用。
    *    同时从函数的签名可以看到:组件的渲染收到两个方面的影响:父组件传递的props改变+组件自己state
    * (2)如组件如果shouldComponentUpdate返回false,那么render方法不会重新渲染
    * (3)父组件的SCU一定在子组件的SCU之前调用,组件的componentWillReceiveProps在SCU之前调用
    */
  shouldComponentUpdate(nextProps, nextState) {
    console.log("Parent的shouldComponentUpdate为", nextProps, nextState);
    return true;
  }
  /**
    * (1)组件只会被挂载一次,父组件的componentDidMount一定在子组件的ComponentDidMount之后被触发
    */
  componentDidMount() {
    console.log("Parent被挂载");
  }
  /**
    * (1)组件调用了setState,那么会让Parent走一次shouldComponentUpdate
    *
    */
  parentRender = () => {
    this.setState({
      count: ++this.state.count
    });
  };
  render() {
    return (
      <div style={{ border: "1px solid red" }}>
        我是Parent
        <div style={{ border: "1px solid pink" }}>
          <Child1 style={{ border: "1px solid yellow" }} />
          <Child2 />
        </div>
        <button onClick={this.parentRender}>
          点击我让父组件重新渲染{this.state.count}
        </button>
      </div>
    );
  }
}

class Child1 extends React.Component {
  /**
      * (1)子组件重新渲染的时候componentWillReceiveProps在SCU之前被调用
      */
  componentWillReceiveProps(nextProps) {
    console.log("Child1的nextProps为", nextProps);
  }
  shouldComponentUpdate(nextProps, nextState) {
    console.log("Child1的shouldComponentUpdate为", nextProps, nextState);
  }
  componentDidMount() {
    console.log("Child1被挂载");
  }
  render() {
    return <div>我是Child1</div>;
  }
}
class Child2 extends React.Component {
  componentWillReceiveProps(nextProps) {
    console.log("Child2的nextProps为", nextProps);
  }
  shouldComponentUpdate(nextProps, nextState) {
    console.log("Child2的shouldComponentUpdate为", nextProps, nextState);
  }
  componentDidMount() {
    console.log("Child2被挂载");
  }
  render() {
    return <div>我是Child1</div>;
  }
}
// ReactDOM.render(<Parent/>, document.getElementById("example"));

componentWillReceiveProps触发的条件

使用React-Router后hash跳转会触发componentWillReceiveProps的坑

通常对于componentWillReceiveProps,认为是外层Update时才会触发。
对于使用了React-Router的场景,通常也会理解为,当路由发生跳转时才会触发。
而实际上,当页面发生hash跳转(例如点击了XXX)时,虽然路由没有跳转,但也会触发componentWillReceiveProps。

场景

使用了componentWillReceiveProps
在点击诸如<a href='#xxx'>XXX</a>时会触发componentWillReceiveProps
实际不希望此时触发

原因

react的componentWillReceiveProps触发实际受2个条件制约

Component.props更新
Component.context更新

而每次URL的pathname变化或hash变化,React-Router都会触发context里的router变化。
从而会触发componentWillReceiveProps

setState 的‘同步’与‘异步’

  1. setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
  2. setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
发布了77 篇原创文章 · 获赞 7 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_37653449/article/details/89330453