Loading

♠ React的特点

React的特点

声明式编程

声明式编程:

  • 声明式编程是目前整个大前端开发的模式:Vue、React、Flutter、SwiftUI;
  • 它允许我们只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面;

组件化开发

组件化开发:

  • 组件化开发页面目前前端的流行趋势,我们会将复杂的界面拆分成一个个小的组件;

多平台适配

多平台适配:

  • 2013年,React发布之初主要是开发Web页面;
  • 2015年,Facebook推出了ReactNative,用于开发移动端跨平台;(虽然目前Flutter非常火爆,但是还是有很多公司在使用ReactNative);
  • 2017年,Facebook推出ReactVR,用于开发虚拟现实Web应用程序;(随着5G的普及,VR也会是一个火爆的应用场景);

Hello React案例

查看代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <h2 class="title"></h2>
  <button class="btn">改变文本</button>

  <script>
    // 命令式编程: 每做一个操作,都是给计算机(浏览器)一步步命令
    // 声明式编程:
    // 1.定义数据
    let message = "Hello World";

    // 2.将数据显示在h2元素中
    const titleEl = document.getElementsByClassName("title")[0];
    titleEl.innerHTML = message;

    // 3.点击按钮,界面的数据发生改变
    const btnEl = document.getElementsByClassName("btn")[0];
    btnEl.addEventListener("click", e => {
      message = "Hello React";
      titleEl.innerHTML = message;
    })

  </script>

</body>
</html>
查看代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>

  <div>Header</div>

  <div id="app">dafdasf</div>

  <div>Footer</div>
  
  <!-- 添加React的依赖 -->
  <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  <!-- 开始开发 -->
  <!-- 注意事项: 使用jsx, 并且希望script中的jsx的代码被解析, 必须在script标签中添加一个属性  -->
  <script type="text/babel">
    // <h2></h2>: jsx代码
    let message = "Hello World";
    ReactDOM.render(<h2>{message}</h2>, document.getElementById("app"))
  </script>

</body>
</html>

React开发依赖

开发React必须依赖三个库:

  • react:包含react所必须的核心代码
  • react-dom:react渲染在不同平台所需要的核心代码
  • babel:将jsx转换成React代码的工具

第一次接触React会被它繁琐的依赖搞蒙,对于Vue来说,我们只是依赖一个vue.js文件即可,但是react居然要依赖三个库。

  • 其实呢,这三个库是各司其职的,目的就是让每一个库只单纯做自己的事情:
  • 在React的0.14版本之前是没有react-dom这个概念的,所有功能都包含在react里。
  • 为什么要进行拆分呢?原因就是react-native。
  • react包中包含了react和react-native所共同拥有的核心代码。

react-dom针对web和native所完成的事情不同:

  • web端:react-dom会讲jsx最终渲染成真实的DOM,显示在浏览器中
  • native端:react-dom会讲jsx最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton)。
查看代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <div id="app">
  </div>

  <!-- 添加React的依赖 -->
  <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  <!-- 开始开发 -->
  <!-- 注意事项: 使用jsx, 并且希望script中的jsx的代码被解析, 必须在script标签中添加一个属性  -->
  <!-- jsx特点: 多个标签最外层(根)只能有一个标签 -->
  <script type="text/babel">
    let message = "Hello World";

    function btnClick() {
      message = "Hello React";
      console.log(message);
      render();
    }

    // <h2></h2>: jsx代码
    function render() {
      ReactDOM.render(
        <div>
          <h2>{message}</h2>
          <button onClick={btnClick}>改变文本</button>
        </div>,
        document.getElementById("app")
      );
    }

    render();
  </script>

</body>

</html>

认识Babel

babel是什么呢?

  • Babel ,又名 Babel.js
  • 是目前前端使用非常广泛的编辑器、转移器。
  • 比如当下很多浏览器并不支持ES6的语法,但是确实ES6的语法非常的简洁和方便,我们开发时希望使用它。
  • 那么编写源码时我们就可以使用ES6来编写,之后通过Babel工具,将ES6转成大多数浏览器都支持的ES5的语法。

React和Babel的关系:

  • 默认情况下开发React其实可以不使用babel。
  • 但是前提是我们自己使用 React.createElement 来编写源代码,它编写的代码非常的繁琐和可读性差。
  • 那么我们就可以直接编写jsx(JavaScript XML)的语法,并且让babel帮助我们转换成React.createElement。

第一步:在界面上通过React显示一个Hello World

注意:这里我们编写React的script代码中,必须添加 type="text/babel",作用是可以让babel解析jsx的语法

ReactDOM.render函数:

  • 参数一:传递要渲染的内容,这个内容可以是HTML元素,也可以是React的组件
  • 参数二:将渲染的内容,挂载到哪一个HTML元素上这里我们已经提定义一个id为app的div

我们可以通过{}语法来引入外部的变量或者表达式

组件化开发

整个逻辑其实可以看做一个整体,那么我们就可以将其封装成一个组件:

  • 我们说过 ReactDOM.render 第一参数是一个HTML原生或者一个组件;
  • 所以我们可以先将之前的业务逻辑封装到一个组件中,然后传入到 ReactDOM.render 函数中的第一个参数;

这里我们暂时使用类的方式封装组件:

  • 1.定义一个类(类名大写,组件的名称是必须大写的,小写会被认为是HTML元素),继承自React.Component
  • 2.实现当前组件的render函数render当中返回的jsx内容,就是之后React会帮助我们渲染的内容
查看代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <div id="app">
  </div>

  <!-- 添加React的依赖 -->
  <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  <!-- 开始开发 -->
  <script type="text/babel">
    // 封装App组件
    class App extends React.Component {
      constructor() {
        super();
        // this.message = "Hello World";
        this.state = {
          message: "Hello World"
        }
      }

      render() {
        return (
          <div>
            <h2>{this.state.message}</h2>
            <button onClick={this.btnClick.bind(this)}>改变文本</button>
          </div>
        )
      }

      btnClick() {
        // this.message = "Hello React";
        // this.state.message = "Hello React";
        // console.log(this.state);
        this.setState({
          message: "Hello React"
        })
      }
    }

    // 渲染组件
    ReactDOM.render(<App/>, document.getElementById("app"));
  </script>

</body>

</html>

数据依赖

在组件中的数据,我们可以分成两类:

  • 参与界面更新的数据:当数据变量时,需要更新组件渲染的内容
  • 不参与界面更新的数据:当数据变量时,不需要更新将组建渲染的内容

参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象的state中

  • 我们可以通过在构造函数中 this.state = {定义的数据}
  • 当我们的数据发生变化时,我们可以调用 this.setState 来更新数据,并且通知React进行update操作,在进行update操作时,就会重新调用render函数,并且使用最新的数据,来渲染界面

事件绑定

在类中直接定义一个函数,并且将这个函数绑定到html原生的onClick事件上,当前这个函数的this指向的是谁呢?默认情况下是undefined

  • 因为在正常的DOM操作中,监听点击,监听函数中的this其实是节点对象(比如说是button对象);
  • 这次因为React并不是直接渲染成真实的DOM,我们所编写的button只是一个语法糖,它的本质React的Element对象;
  • 那么在这里发生监听的时候,react给我们的函数绑定的this,默认情况下就是一个undefined;

我们在绑定的函数中,可能想要使用当前对象,比如执行 this.setState 函数,就必须拿到当前对象的this

  • 我们就需要在传入函数时,给这个函数直接绑定this
  • 类似于下面的写法: <button onClick={this.changeText.bind(this)}>改变文本</button>

认识JSX

JSX

  • JSX是一种JavaScript的语法扩展(eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法;
  • 它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用;
  • 它不同于Vue中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind);

React认为渲染逻辑本质上与其他UI逻辑存在内在耦合

  • 比如UI需要绑定事件(button、a原生等等);
  • 比如UI中需要展示数据状态,在某些状态发生改变时,又需要改变UI;

JSX的书写规范:

  • JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div原生(或者使用后面我们学习的Fragment);
  • 为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写;
  • JSX中的标签可以是单标签,也可以是双标签;

注意:如果是单标签,必须以/>结尾;

JSX嵌入变量

  • 情况一:当变量是Number、String、Array类型时,可以直接显示
  • 情况二:当变量是null、undefined、Boolean类型时,内容为空;
    • 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
    • 转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;
  • 情况三:对象类型不能作为子元素(not valid as a React child)

JSX嵌入表达式

  • 运算表达式
  • 三元运算符
  • 执行一个函数
查看代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
    
  <div id="app"></div>

  <script src="../react/react.development.js"></script>
  <script src="../react/react-dom.development.js"></script>
  <script src="../react/babel.min.js"></script>

  <script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          // 1.在{}中可以正常显示显示的内容
          name: "why", // String
          age: 18, // Number
          names: ["abc", "cba", "nba"], // Array

          // 2.在{}中不能显示(忽略)
          test1: null, // null
          test2: undefined, // undefined
          test3: true, // Boolean
          flag: true,

          // 3.对象不能作为jsx的子类
          friend: {
            name: "kobe",
            age: 40
          }
        }
      }

      render() {
        return (
          <div>
            <h2>{this.state.name}</h2>
            <h2>{this.state.age}</h2>
            <h2>{this.state.names}</h2>

            <h2>{this.state.test1 + ""}</h2>
            <h2>{this.state.test2 + ""}</h2>
            <h2>{this.state.test3.toString()}</h2>

            <h2>{this.state.flag ? "你好啊": null}</h2>

            <h2>{this.state.friend}</h2>
          </div>
        )
      }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
  </script>

</body>
</html>
查看代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
    
  <div id="app"></div>

  <script src="../react/react.development.js"></script>
  <script src="../react/react-dom.development.js"></script>
  <script src="../react/babel.min.js"></script>

  <script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          firstname: "kobe",
          lastname: "bryant",
          isLogin: false
        }
      }

      render() {
        const { firstname, lastname, isLogin } = this.state;

        return (
          <div>
            {/*1.运算符表达式*/}
            <h2>{ firstname + " " + lastname }</h2>
            <h2>{20 * 50}</h2>

            {/*2.三元表达式*/}
            <h2>{ isLogin ? "欢迎回来~": "请先登录~" }</h2>

            {/*3.进行函数调用*/}
            <h2>{this.getFullName()}</h2>
          </div>
        )
      }

      getFullName() {
        return this.state.firstname + " " + this.state.lastname;
      }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
  </script>

</body>
</html>

React事件绑定

如果原生DOM原生有一个监听事件,我们可以如何操作呢?

  • 方式一:获取DOM原生,添加监听事件;
  • 方式二:在HTML原生中,直接绑定onclick;

在React中是如何操作呢?

  • 我们来实现一下React中的事件监听,这里主要有两点不同
    • React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
  • 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行;

this的绑定问题

在事件执行后,我们可能需要获取当前类的对象中相关的属性,这个时候需要用到this

  • 如果我们这里直接打印this,也会发现它是一个undefined

为什么是undefined呢?

  • 原因是btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数;
  • 而它内部调用时,并不知道要如何绑定正确的this;

如何解决this的问题呢?

  • 方案一:bind给btnClick显示绑定this
  • 方案二:使用 ES6 class fields 语法
  • 方案三:事件监听时传入箭头函数(推荐)
查看代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
    
  <div id="app"></div>

  <script src="../react/react.development.js"></script>
  <script src="../react/react-dom.development.js"></script>
  <script src="../react/babel.min.js"></script>

  <script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          message: "你好啊",
          counter: 100
        }

        this.btnClick = this.btnClick.bind(this);
      }

      render() {
        return (
          <div>
            {/* 1.方案一: bind绑定this(显示绑定) */}
            <button onClick={this.btnClick}>按钮1</button>
            <button onClick={this.btnClick}>按钮2</button>
            <button onClick={this.btnClick}>按钮3</button>

            {/* 2.方案二: 定义函数时, 使用箭头函数 */}
            <button onClick={this.increment}>+1</button>

            {/* 2.方案三(推荐): 直接传入一个箭头函数, 在箭头函数中调用需要执行的函数,尖头函数不绑定this
            执行decrement,去上层作用域找,也就是App这个组件实例           
            */}
            <button onClick={() => { this.decrement("why") }}>-1</button>
          </div>
        )
      }

      btnClick() {
        console.log(this.state.message);
      }

      // increment() {
      //   console.log(this.state.counter);
      // }
      // 箭头函数中永远不绑定this
      // ES6中给对象增加属性: class fields
      increment = () => {
        console.log(this.state.counter);
      }

      decrement(name) {
        console.log(this.state.counter, name);
      }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
  </script>

</body>
</html>

事件参数传递

在执行事件函数时,有可能我们需要获取一些参数信息:比如event对象、其他参数。

情况一:获取event对象

  • 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
  • 假如我们用不到this,那么直接传入函数就可以获取到event对象;

情况二:获取更多参数

  • 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;

React条件渲染

某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:

  • 在vue中,我们会通过指令来控制:比如v-if、v-show;
  • 在React中,所有的条件判断都和普通的JavaScript代码一致;

方式一:条件判断语句

  • 适合逻辑较多的情况

方式一:条件判断语句

  • 适合逻辑较多的情况

方式二:三元运算符

  • 适合逻辑比较简单

与运算符&&

  • 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染;

v-show的效果

  • 主要是控制display属性是否为none
查看代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <h2>我是标题</h2>
  <H2>我是标题</H2>
  <DIV>我是div元素</DIV>

  <div id="app"></div>

  <script src="../react/react.development.js"></script>
  <script src="../react/react-dom.development.js"></script>
  <script src="../react/babel.min.js"></script>

  <script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          isLogin: true
        }
      }

      render() {
        const { isLogin } = this.state;

        // 1.方案一:通过if判断: 逻辑代码非常多的情况
        let welcome = null;
        let btnText = null;
        if (isLogin) {
          welcome = <h2>欢迎回来~</h2>
          btnText = "退出";
        } else {
          welcome = <h2>请先登录~</h2>
          btnText = "登录";
        }

        return (
          <div>
            <div>我是div元素</div>

            {welcome}
            {/* 2.方案二: 三元运算符 */}
            <button onClick={e => this.loginClick()}>{isLogin ? "退出" : "登录"}</button>

            <hr />

            <h2>{isLogin ? "你好啊": null}</h2>

            {/* 3.方案三: 逻辑与&& */}
            {/* 逻辑与: 一个条件不成立, 后面的条件都不会进行判断了 */}
            <h2>{ isLogin && "你好啊" }</h2>
            { isLogin && <h2>你好啊</h2> }
          </div>
        )
      }

      loginClick() {
        this.setState({
          isLogin: !this.state.isLogin
        })
      }
    }

    ReactDOM.render(<App />, document.getElementById("app"));
  </script>

</body>

</html>

React列表渲染

在React中并没有像Vue模块语法中的v-for指令,而且需要我们通过JavaScript代码的方式组织数据,转成JSX。

如何展示列表呢?

  • 在React中,展示列表最多的方式就是使用数组的map高阶函数;

很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:

  • 比如过滤掉一些内容:filter函数
  • 比如截取数组中的一部分内容:slice函数

JSX的本质

实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。所有的jsx最终都会被转换成React.createElement的函数调用。

createElement需要传递三个参数:

参数一:type

当前ReactElement的类型;

  • 如果是标签元素,那么就使用字符串表示 “div”;
  • 如果是组件元素,那么就直接使用组件的名称;

参数二:config

  • 所有jsx中的属性都在config中以对象的属性和值的形式存储

参数三:children

  • 存放在标签中的内容,以children数组的方式进行存储;
  • 当然,如果是多个元素呢?React内部有对它们进行处理

Babel官网查看

我们知道默认jsx是通过babel帮我们进行语法转换的,所以我们之前写的jsx代码都需要依赖babel。可以在babel的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react

我们自己来编写React.createElement代码:

我们通过 React.createElement 最终创建出来一个 ReactElement对象

这个ReactElement对象是什么作用呢?React为什么要创建它呢?

  • 原因是React利用ReactElement对象组成了一个JavaScript的对象树;
  • JavaScript的对象树就是大名鼎鼎的虚拟DOM(Virtual DOM);

为什么使用虚拟DOM

为什么要采用虚拟DOM,而不是直接修改真实的DOM呢?

  • 很难跟踪状态发生的改变:原有的开发模式,我们很难跟踪到状态发生的改变,不方便针对我们应用程序进行调试;
  • 操作真实DOM性能较低:传统的开发模式会进行频繁的DOM操作,而这一的做法性能非常的低;

DOM操作性能非常低:

  • 首先,document.createElement本身创建出来的就是一个非常复杂的对象;
    • https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement
  • 其次,DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作;

我们举个例子:比如我们有一组数组需要渲染:[0, 1, 2, 3, 4],我们会怎么做呢? 我们可以通过ul和li将它们展示出来.后来,我们又增加了5条数据:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].方式一:重新遍历整个数组(不推荐).方式二:在ul后面追加另外5个li.

上面这段代码的性能怎么样呢?非常低效

  • 因为我们通过 document.createElement 创建元素,再通过 ul.appendChild(li) 渲染到DOM上,进行了多次DOM操作;
  • 对于批量操作的,最好的办法不是一次次修改DOM,而是对批量的操作进行合并;(比如可以通过DocumentFragment进行合并);

React官方的说法:Virtual DOM 是一种编程理念。

  • 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
  • 我们可以通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation);

 

posted @ 2022-01-24 00:18  sunflower-js  阅读(42)  评论(0编辑  收藏  举报