zoukankan      html  css  js  c++  java
  • react(一)

    1、es6的class、箭头函数

    1)ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
    }
    
    var p1 = new Point(1, 1);
    console.log(p1.toString()); // (1, 1)
    console.log(typeof Point, Point === Point.prototype.constructor); // function true

    上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5 的构造函数Point,对应 ES6 的Point类的构造方法

    Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错

    上面代码表明,类的数据类型就是函数,类本身就指向构造函数。使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致

    构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面

    class Point {
      constructor() {
        // ...
      }
      toString() {
        // ...
      }
    }
    
    // 等同于
    Point.prototype = {
      constructor() {},
      toString() {}
    };

    类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

    var p1 = new Point(2,3);
    var p2 = new Point(3,2);
    p1.__proto__ === p2.__proto__; //true

    上面代码中,p1p2都是Point的实例,它们的原型都是Point.prototype,所以__proto__属性是相等的。

    __proto__ 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。

    2)Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

    class ColorPoint extends Point {
    }

    上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类。

    class ColorPoint extends Point {
      constructor(x, y, color) {
        super(x, y); // 调用父类的constructor(x, y)
        this.color = color;
      }
      toString() {
        return this.color + ' ' + super.toString(); // 调用父类的toString()
      }
    }
    let cp = new ColorPoint(1,1,'red'); 
    console.log(cp, cp.toString()); // ColorPoint {x: 1, y: 1, color: "red"} "red (1, 1)"

    子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

    如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

    class ColorPoint extends Point {
    }
    // 等同于
    class ColorPoint extends Point {
      constructor(...args) {
        super(...args);
      }
    }
    let cp = new ColorPoint(25, 8, 'green');
    cp instanceof ColorPoint // true
    cp instanceof Point // true

    上面代码中,子类ColorPoint实例对象cp同时是ColorPointPoint两个类的实例,这与 ES5 的行为完全一致。

     3)ES6 允许使用“箭头”(=>)定义函数

    var f = v => v;
    // 等同于
    var f = function (v) {
      return v;
    };

    如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

    var f = () => 5;
    // 等同于
    var f = function () { return 5 };
    
    var sum = (num1, num2) => num1 + num2;
    // 等同于
    var sum = function(num1, num2) {
      return num1 + num2;
    };

    如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

    var sum = (num1, num2) => { return num1 + num2; }

    由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错

    // 报错
    let getTempItem = id => { id: id, name: "Temp" };
    
    // 不报错
    let getTempItem = id => ({ id: id, name: "Temp" });

    2、react

    参考:http://react.css88.com/

    • 第一个简单的react程序

    参考:http://react.css88.com/docs/cdn-links.html

    <div id="root"></div>
    <!-- react.js 是 React 的核心库,react-dom.js 是提供与 DOM 相关的功能 -->
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <!-- 上面的版本只适合开发环境,不适合生产环境。下面为压缩优化版本
    <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> -->
    <!-- babel:将 JSX 语法转为 JavaScript 语法,这一步很消耗时间,实际上线的时候,应该将它放到服务器完成 -->
    <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
    
    <!-- <script> Uncaught SyntaxError: Unexpected token <-->
    <!-- 
    控制台有警告:You are using the in-browser Babel transformer. Be sure to precompile your scripts for production
    网页中实时将ES6代码转为ES5,对性能会有影响。生产环境需要加载已经转码完成的脚本 
    React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel"
    -->
    <script type="text/babel">
    const element = <h1>Hello, world</h1>;
    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    </script>

    浏览器渲染结果:

    a)crossorigin(实现更好的错误处理体验)

    引入跨域的脚本(比如用了 apis.google.com 上的库文件),如果这个脚本有错误,因为浏览器的限制(根本原因是协议的规定),是拿不到错误信息的。当本地尝试使用 window.onerror 去记录脚本的错误时,跨域脚本的错误只会返回 Script error。但 HTML5 新的规定,是可以允许本地获取到跨域脚本的错误信息,但有两个条件:一是跨域脚本的服务器必须通过 Access-Control-Allow-Origin 头信息允许当前域名可以获取错误信息,二是当前域名的 script标签也必须指明 src 属性指定的地址是支持跨域的地址,也就是 crossorigin 属性。(参考:https://www.chrisyue.com/what-the-hell-is-crossorigin-attribute-in-html-script-tag.html

    b)更通用格式 UMD(Universal Module Definition)-希望提供一个前后端跨平台的解决方案

     UMD的实现很简单,先判断是否支持NodeJS模块格式(exports是否存在),存在则使用NodeJS模块格式。再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。前两个都不存在,则将模块公开的全局(window或global)。

    c)ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。

    • JSX(JavaScript eXtension)- 允许 HTML 与 JavaScript 混写

    参考:https://segmentfault.com/q/1010000003877594/a-1020000003878406

    JavaScript 的一种扩展语法,推荐在 React 中使用这种语法来描述 UI 信息。React为了代码的可读性、更方便地创建虚拟DOM等原因,加入了一些类似XML的语法扩展。JSX是可选的,对于使用 React 而言不是必须的。

    JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析

    JSX代码并不能直接运行,需要将它编译成正常的JavaScript表达式才能运行,jsxTransformer.js就是这一编译器的角色。React官方博客在2015年6月发布了一篇文章,声明用于JSX语法解析的编译器JSTransform已经过期,不再维护,React JS和React Native已经全部采用第三方Babel的JSX编译器实现。Babel作为专门的JavaScript语法编译工具,提供了更为强大的功能。

    function formatName(user) {
      return user.firstName + ' ' + user.lastName;
    }
    const user = { firstName: 'Harper', lastName: 'Perez' };
    const title = '我是h1';
    const element = (
        <div>
            <h1 title={title}>Hello, {formatName(user)}!</h1>
            <img className="img" src="http://img2.imgtn.bdimg.com/it/u=3723784612,2573513060&fm=200&gp=0.jpg"/>
        </div>
    );
    
    ReactDOM.render(
      element,
      document.getElementById('root')
    );

    为便于阅读,我们将 JSX 分割成多行。我们推荐使用括号将 JSX 包裹起来,虽然这不是必须的,但这样做可以避免分号自动插入的陷阱。

    在属性中嵌入 JavaScript 表达式时,不要使用引号来包裹大括号。否则,JSX 将该属性视为字符串字面量而不是表达式。对于字符串值你应该使用引号,对于表达式你应该使用大括号,但两者不能同时用于同一属性

    jsx里的标签都应该闭合。

    比起 HTML , JSX 更接近于 JavaScript , 所以 React DOM 使用驼峰属性命名约定, 而不是HTML属性名称。例如,class 在JSX中变为className。

    render()函数中返回的所有元素需要包裹在一个"根"元素里面。

    React DOM 会将元素及其子元素与之前版本逐一对比, 并只对有必要更新的 DOM 进行更新, 以达到 DOM 所需的状态。

    • 组件(Components) 和 属性(Props)

    Props 是只读的:无论你用函数或类的方法来声明组件, 它都无法修改其自身 props.

    1)函数式组件

    所有的React组件都有一个render函数,它指定了React组件的HTML输出。

    <div id="root"></div>

    以下代码在页面上渲染 “Hello, Sara” 

    // 接收一个 props 参数, 并返回一个 React 元素
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    // 用户定义组件(Welcome 组件)将 JSX 属性以一个单独对象的形式传递给相应的组件,我们将其称为 “props” 对象
    const element = <Welcome name="Sara" />;
    ReactDOM.render(
      element,
      document.getElementById('root')
    );

    组件名称总是以大写字母开始,否则会报错;组件可以在它们的输出中引用其它组件

    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    function App() {
      return (
        <div>
          <Welcome name="Sara" />
          <Welcome name="Cahal" />
          <Welcome name="Edite" />
        </div>
      );
    }
    
    ReactDOM.render(
      <App />,
      document.getElementById('root')
    );

    2)提取组件

    不要害怕把一个组件分为多个更小的组件。提取组件可能看起来是一个繁琐的工作,但是在大型的 Apps 中可以回报给我们的是大量的可复用组件。一个好的经验准则是如果你 UI 的一部分需要用多次 ,或者本身足够复杂,最好的做法是使其成为可复用组件

    props是只读的。

      所有 React 组件都必须是纯函数,并禁止修改其自身 props 。当然,应用 UI 总是动态的,并且随时有可以改变。state(状态) 允许 React 组件在不违反上述规则的情况下, 根据用户操作, 网络响应, 或者其他, 来动态地改变其输出。

    3)类组件

    类组件允许我们在其中添加本地状态和生命周期钩子

    class Clock extends React.Component {
      // 添加一个类构造函数初始化this.state
      constructor(props) {
        super(props);
        this.state = {date: new Date()};
      }
      // 挂载—组件输出被渲染到 DOM 之后运行(设置定时器)
      componentDidMount() {
        // 如果需要存储一些不用于视觉输出的内容,则可以手动向类中添加额外的字段,如下面的timerID
        // 如果在 render() 方法中没有被引用, 它不应该出现在 state 中
        this.timerID = setInterval(
          () => this.tick(),
          1000
        );
      }
      // 卸载—DOM 被销毁时运行(清除计时器)
      componentWillUnmount() {
        clearInterval(this.timerID);
      }
      tick() {
        this.setState({ date: new Date() });
      // this.state.date = new Date(); //这样将不会重新渲染一个组件
      }
      render() {
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Clock />,
      document.getElementById('root')
    );

    分析:

    1. 当 <Clock /> 被传入 ReactDOM.render() 时, React 会调用 Clock组件的构造函数。 因为 Clock 要显示的是当前时间,所以它将使用包含当前时间的对象来初始化 this.state 。我们稍后会更新此状态。

    2. 然后 React 调用了 Clock 组件的 render() 方法。 React 从该方法返回内容中得到要显示在屏幕上的内容。然后,React 然后更新 DOM 以匹配 Clock 的渲染输出。

    3. 当 Clock 输出被插入到 DOM 中时,React 调用 componentDidMount() 生命周期钩子。在该方法中,Clock 组件请求浏览器设置一个定时器来一次调用 tick()

    4. 浏览器会每隔一秒调用一次 tick()方法。在该方法中, Clock 组件通过 setState() 方法并传递一个包含当前时间的对象来安排一个 UI 的更新。通过 setState(), React 得知了组件 state(状态)的变化, 随即再次调用 render() 方法,获取了当前应该显示的内容。 这次,render() 方法中的 this.state.date 的值已经发生了改变, 从而,其输出的内容也随之改变。React 于是据此对 DOM 进行更新。

    5. 如果通过其他操作将 Clock 组件从 DOM 中移除了, React 会调用 componentWillUnmount()生命周期钩子, 所以计时器也会被停止。

    正确地使用 State(状态)

     a)不要直接修改 state。

    例如,这样将不会重新渲染一个组件:this.state.date = new Date();

    用 setState() 代替:this.setState({ date: new Date() });

    唯一可以分配 this.state 的地方是构造函数。

    b)state更新可能是异步的

    React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新

    class Clock extends React.Component {
      // 添加一个类构造函数初始化this.state
      constructor(props) {
       super(props);
    this.state = { date: new Date(), counter: 0 }; } // 挂载—组件输出被渲染到 DOM 之后运行(设置定时器) componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } // 卸载—DOM 被销毁时运行(清除计时器) componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); this.setState((prevState, props) => ({ counter: prevState.counter + parseInt(props.increment) })); } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> <h2>counter:{this.state.counter}</h2> </div> ); } } ReactDOM.render( <Clock increment = '2'/>, document.getElementById('root') );

    因为 this.props 和 this.state 可能是异步更新的,你不能依赖他们的值计算下一个state。例如, 以下代码可能导致 counter(计数器)更新失败

    // 错误
    this.setState({
      counter: this.state.counter + this.props.increment,
    });

    要弥补这个问题,使用另一种 setState() 的形式,它接受一个函数而不是一个对象。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数

    // 正确
    this.setState((prevState, props) => ({
      counter: prevState.counter + props.increment
    }));

     c)state更新会被合并

    当你调用 setState(), React 将合并你提供的对象到当前的状态中。this.setState({comments})会完全替换this.state.comments

    4)数据向下流动

    一个组件可以选择将 state(状态) 向下传递,作为其子组件的 props(属性)。这通常称为一个“从上到下”,或者“单向”的数据流。任何 state始终由某个特定组件所有,并且从该 state导出的任何数据 或 UI 只能影响树中 “下方” 的组件

    class Clock extends React.Component {
      // ...
      render() {
        return (
          <div>
            <FormattedDate date={this.state.date}/>
          </div>
        );
      }
    }
    function FormattedDate(props) {
        return <h2>It is {props.date.toLocaleTimeString()}.</h2>
    }
    
    ReactDOM.render(
      <Clock />,
      document.getElementById('root')
    );
  • 相关阅读:
    Swift的闭包(一):闭包简介、闭包表达式的优化
    iOS 用户体验之音频
    【iOS】Core Bluetooth
    【iOS】3D Touch
    golang中channel的超时处理
    Objective-C 中的 BOOL
    2015年总结
    load vs. initialize
    正则表达式
    When does layoutSubviews get called?
  • 原文地址:https://www.cnblogs.com/colorful-coco/p/9200650.html
Copyright © 2011-2022 走看看