Skip to content

Files

Latest commit

 

History

History
710 lines (580 loc) · 21.8 KB

2.jsx.md

File metadata and controls

710 lines (580 loc) · 21.8 KB

jsx 语法

ES6 的 Class

在ES6之前,我们通过function来定义类,但是这种模式一直被很多从其他编程语言(比如Java、C++、OC等等)转到JavaScript的人所不适应。原因是,大多数面向对象的语言,都是使用class关键字来定义类的。而JavaScript也从ES6开始引入了class关键字,用于定义一个类。ES6之前定义一个Person类:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.running = function() {
  console.log(this.name + this.age + "running");
}

var p = new Person("why", 18);
p.running();

转换成ES6中的类如何定义呢?类中有一个constructor构造方法,当我们通过new关键字调用时,就会默认执行这个构造方法。构造方法中可以给当前对象添加属性,类中也可以定义其他方法,这些方法会被放到Person类的prototype上。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  running() {
    console.log(this.name + this.age + "running");
  }
}

const p = new Person("why", 18);
p.running();

类的继承

继承是面向对象的一大特性,可以减少我们重复代码的编写,方便公共内容的抽取(也是很多面向对象语言中,多态的前提)。ES6中增加了extends关键字来作为类的继承。我们先写两个类没有继承的情况下,它们存在的重复代码:Person类和Student类

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  running() {
    console.log(this.name, this.age, "running");
  }
}

class Student {
  constructor(name, age, sno, score) {
    this.name = name;
    this.age = age;
    this.sno = sno;
    this.score = score;
  }

  running() {
    console.log(this.name, this.age, "running");
  }

  studying() {
    console.log(this.name, this.age, this.sno, this.score, "studing");
  }
}

我们可以使用继承来简化代码:注意:在constructor中,子类必须通过super来调用父类的构造方法,对父类进行初始化,否则会报错。

class Student1 extends Person {
  constructor(name, age, sno, score) {
    super(name, age);
    this.sno = sno;
    this.score = score;
  }

  studying() {
    console.log(this.name, this.age, this.sno, this.score, "studing");
  }
}

const stu1 = new Student1("why", 18, 110, 100);
stu1.studying();

案例:电影列表

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <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>

    <!-- 编写 react 代码 -->
    <script type="text/babel">
        class App extends React.Component {
            constructor () {
                super();

                this.state = {
                    message: 'Hello World',
                    movies: ['大话西游', '盗梦空间', '星际穿越', '流浪地球']
                }
            }

            render () {
                const liArr = [];
                for (let i of this.state.movies) {
                    liArr.push(<li>{i}</li>)
                }

                return (
                    <div>
                        <h2>电影列表</h2>
                        <ul>
                            {liArr}
                        </ul>

                        
                        <h2>电影列表2</h2>
                        <ul>
                            {this.state.movies.map(i => (<li>{i}</li>))}
                        </ul>
                    </div>
                )
            }
        }

        ReactDOM.render(<App />, document.getElementById('app'))
    </script>
</body>
</html>

案例:计数器

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <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>

    <!-- 编写 react 代码 -->
    <script type="text/babel">
        class App extends React.Component {
            constructor () {
                super();

                this.state = {
                    count: 0
                }
            }

            increase () {
                console.log('+1')
                this.setState({
                    count: this.state.count + 1
                })
            }

            decrease () {
                console.log('-1')
                this.setState({
                    count: this.state.count - 1
                })
            }

            render () {

                return (
                    <div>
                        <h2>当前计数:{this.state.count}</h2>
                        <button onClick={this.increase.bind(this)}>+1</button>
                        <button onClick={this.decrease.bind(this)}>-1</button>
                    </div>
                )
            }
        }

        ReactDOM.render(<App />, document.getElementById('app'))
    </script>
</body>
</html>

jsx

JSX是什么?

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

为什么React选择了JSX?

  • React认为渲染逻辑本质上与其他UI逻辑存在内在耦合 比如UI需要绑定事件(button、a原生等等);比如UI中需要展示数据状态,在某些状态发生改变时,又需要改变UI;他们之间是密不可分,所以React没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component);在这里,我们只需要知道,JSX其实是嵌入到JavaScript中的一种结构语法;

JSX的书写规范:

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

JSX嵌入表达式

如果我们jsx中的内容是动态的,我们可以通过表达式来获取:大括号内可以是变量、字符串、数组、函数调用等任意js表达式;

<div>{this.state.data}</div>

jsx中的注释

jsx是嵌入到JavaScript中的一种语法,所以在编写注释时,需要通过JSX的语法来编写:

{/* 这是一段注释 */}

JSX嵌入变量

  • 情况一:当变量是Number、String、Array类型时,可以直接显示
  • 情况二:当变量是null、undefined、Boolean类型时,内容为空;如果希望可以显示null、undefined、Boolean,那么需要转成字符串;转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;
  • 情况三:对象类型不能作为子元素(not valid as a React child),如果放到花括号中放入 jsx 中直接会报错。
  • 补充:为什么null、undefined、Boolean在JSX中要显示为空内容呢?原因是在开发中,我们会进行很多的判断;在判断结果为false时,不显示一个内容;在判断结果为true时,显示一个内容;
class App extends React.Component {
  constructor () {
      super();
      this.state = {
          data1: 6,
          data2: 'Hello World',
          data3: [123, 456, 789],
          data4: undefined,
          data5: null,
          data6: true,
          data7: false,
          obj: {
              name: 'tom',
              age: 28
          }
      }
  }

  render () {
      const { data1, data2, data3, data4, data5, data6, data7} = this.state;
      return (
          <div>
              {/* 可以正常显示的类型 */}
              <div>{data1}</div>
              <div>{data2}</div>
              <div>{data3}</div>
              <br />

              {/* 不会显示的类型 */}
              <div>{data4}</div>
              <div>{data5}</div>
              <div>{data6}</div>
              <br />

              {/* 对象直接放入大括号中会报错 */}

              <div>{this.state.obj}</div>

              {/* Objects are not valid as a React child */}
          </div>
      )
  }
}

ReactDOM.render(<App />, document.getElementById('app'))

jsx 嵌入表达式

  • 运算表达式
  • 三元运算符
  • 执行一个函数
class App extends React.Component {
    constructor () {
        super();
        this.state = {
            info: {
                name: 'tom',
                age: '28'
            },
            isLogin: true
        }
    }

    sum (a, b) {
        return a + b;
    }

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

        return (
            <div>
                {/* 表达式 */}
                <div>{info.name + ' ' + info.age}</div>
                <div>{20 + 100}</div>
                {/* 三元运算符 */}
                <div>{isLogin ? '已登录' : '请登录'}</div>
                {/* 函数调用 */}
                <div>{this.sum(1, 6)}</div>
            </div>
        )
    }
}

ReactDOM.render(<App />, document.getElementById('app'))

jsx绑定属性

  • 比如元素都会有title属性
  • 比如img元素会有src属性
  • 比如a元素会有href属性
  • 比如元素可能需要绑定class
  • 比如原生使用内联样式style
class App extends React.Component {
    constructor () {
        super();
        this.state = {
            title: '标题',
            imgUrl: 'http://p1.music.126.net/z3mxHzSvXoMPhWYYPWAstA==/109951166006554807.jpg',
            link: 'http://www.baidu.com',
            active: true
        }
    }

    getSizeImg (img, size) {
        return `${img}?param=${size}x${size}`
    }

    render () {
        const { title, imgUrl, link, active } = this.state;
        return (
            <div>
                {/* 普通属性 */}
                <h2 title={title}>我是标题</h2>
                <img src={this.getSizeImg(imgUrl, 140)} alt="" />
                <a href={link}>百度</a>

                {/* 绑定class */}
                <div className="box title">className</div>
                <div className={'box title' + (active ? 'active' : '')}>className</div>
                <label htmlFor=""></label>

                {/* 绑定style */}
                <div style={{color: 'blue', fontSize: '20px'}}>绑定style</div>
            </div>
        )
    }
}

ReactDOM.render(<App />, document.getElementById('app'))

事件绑定

我们来实现一下React中的事件监听,这里主要有两点不同

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
  • 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行;

在事件执行后,我们可能需要获取当前类的对象中相关的属性,这个时候需要用到this如果我们这里直接打印this,也会发现它是一个undefined。为什么是undefined呢?原因是btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数;而它内部调用时,并不知道要如何绑定正确的this;如何解决this的问题呢?

  • 方案一:bind给btnClick显示绑定this
  • 方案二:使用ES6 class fields 语法
  • 方案三:事件监听时传入箭头函数(推荐)
class App extends React.Component {
    constructor () {
        super();
        this.state = {
            message: '你好',
            count: 0
        }

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

    btnClick () {
        console.log(this.state.message, '点击了')
    }

    increase =  () => {
        this.setState({
            count: this.state.count + 1
        });
    }

    decrease () {
        this.setState({
            count: this.state.count - 1
        })
    }

    render () {
        const { message, count } = this.state;
        return (
            <div>
                <div>{message}</div>
                <div>{count}</div>
                {/* 显示绑定 */}
                <button onClick={this.btnClick.bind(this)}>按钮</button>

                {/* 定义函数时使用箭头函数 */}
                <button onClick={this.increase}>+1</button>

                {/* 给事件绑定传递一个箭头函数,在箭头函数中调用要执行的函数 */}
                <button onClick={e => {this.decrease()}}>-1</button>
            </div>
        )
    }
}

ReactDOM.render(<App />, document.getElementById('app'))

事件参数传递

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

  • 情况一:获取event对象
    • 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
    • 假如我们用不到this,那么直接传入函数就可以获取到event对象;
  • 情况二:获取更多参数
    • 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;
class App extends React.Component {
    constructor () {
        super();
        this.state = {
            movies: ['盗梦空间', '流浪地球', '大话西游']
        }

    }

    liClick (item, index, event) {
        console.log(item, index, event)
    }

    btnClick (e) {
        console.log(e)
    }

    render () {
        const { movies } = this.state;
        return (
            <div>
                <button onClick={this.btnClick}>按钮</button>
                <ul>
                    {
                        movies.map((item, index) => (<li onClick={(e) => {this.liClick(item, index, e)}}>{item}</li>))
                    }
                </ul>
            </div>
        )
    }
}

ReactDOM.render(<App />, document.getElementById('app'))

条件渲染

常见的条件渲染的方式有哪些呢?

  • 方式一:条件判断语句 适合逻辑较多的情况
  • 方式二:三元运算符 适合逻辑比较简单
  • 与运算符&& 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染;
  • v-show的效果 主要是控制display属性是否为none
class App extends React.Component {
    constructor () {
        super();
        this.state = {
            isLogin: false
        }
    }

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

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

        {/* 条件判断语句 */}
        if (isLogin) {
            welcome = '欢迎回来'
        } else {
            welcome = '请先登录'
        }

        return (
            <div>
                {welcome}
                {/* 三目运算符 */}
                <button onClick={() => {this.btnClick()}}>{isLogin ? '退出' : '登录'}</button>

                <hr />

                {/* 逻辑与运算符 */}
                <div>{isLogin && '你好!'}</div>
            </div>
        )
    }
}

ReactDOM.render(<App />, document.getElementById('app'))

实现 v-show 的效果:

class App extends React.Component {
    constructor () {
        super();
        this.state = {
            isLogin: false
        }
    }

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

    render () {
        const { isLogin } = this.state;
        return (
            <div>
                <button onClick={() => {this.btnClick()}}>{isLogin ? '退出' : '登录'}</button>
                <h2 style={{display: isLogin ? 'block' : 'none'}}>你好!</h2>
            </div>
        )
    }
}

ReactDOM.render(<App />, document.getElementById('app'))

列表渲染

如何展示列表呢?

  • 在React中,展示列表最多的方式就是使用数组的map高阶函数;
  • 很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:
    • 比如过滤掉一些内容:filter函数
    • 比如截取数组中的一部分内容:slice函数
class App extends React.Component {
    constructor () {
        super();
        this.state = {
            name: ['cba', 'abc', 'nba', 'dna'],
            number: [110, 123, 50, 23, 32, 55, 10, 8, 333]
        }
    }

    render () {
        const { name, number } = this.state
        return (
            <div>
                <ul>
                    {
                        name.map(item => (<li>{item}</li>))
                    }
                </ul>

                <hr />
                <ul>
                    {
                        number.filter(item => item >= 50).map(item => (<li>{item}</li>))
                    }
                </ul>
                <hr />
                <ul>
                    {
                        number.slice(0, 4).map(item => (<li>{item}</li>))
                    }
                </ul>
            </div>
        )
    }
}

ReactDOM.render(<App />, document.getElementById('app'))

jsx 本质

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

  • React.createElement在源码的什么位置呢?

    • ./packages/react/src/ReactElement.js
  • createElement需要传递三个参数:

    • 参数一:type 当前ReactElement的类型;
      • 如果是标签元素,那么就使用字符串表示“div”;
      • 如果是组件元素,那么就直接使用组件的名称;
    • 参数二:config
      • 所有jsx中的属性都在config中以对象的属性和值的形式存储
    • 参数三:children
      • 存放在标签中的内容,以children数组的方式进行存储;
  • 我们知道默认jsx是通过babel帮我们进行语法转换的,所以我们之前写的jsx代码都需要依赖babel。

  • 可以在babel的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react

<div>
    <div className="header">
        <h1 title="标题">我是标题</h1>
    </div>
    <div className="content">
        <h2>我是页面的内容</h2>
        <button>按钮</button>
        <button>+1</button>
        <a href="http://www.baidu.com">百度一下</a>
    </div>
    <div className="footer">
        <p>我是尾部的内容</p>
    </div>
</div>

经过 babel 转换后:

"use strict";

/*#__PURE__*/
React.createElement("div", null, 
/*#__PURE__*/React.createElement("div", {
  className: "header"
}, /*#__PURE__*/React.createElement("h1", {
  title: "\u6807\u9898"
}, "\u6211\u662F\u6807\u9898")),
/*#__PURE__*/React.createElement("div", {
  className: "content"
}, /*#__PURE__*/React.createElement("h2", null, "\u6211\u662F\u9875\u9762\u7684\u5185\u5BB9"), /*#__PURE__*/React.createElement("button", null, "\u6309\u94AE"), /*#__PURE__*/React.createElement("button", null, "+1"), /*#__PURE__*/React.createElement("a", {
  href: "http://www.baidu.com"
}, "\u767E\u5EA6\u4E00\u4E0B")),
/*#__PURE__*/React.createElement("div", {
  className: "footer"
}, /*#__PURE__*/React.createElement("p", null, "\u6211\u662F\u5C3E\u90E8\u7684\u5185\u5BB9")));

虚拟 DOM 创建过程

我们通过React.createElement 最终创建出来一个ReactElement对象:这个ReactElement对象是什么作用呢?React为什么要创建它呢?原因是React利用ReactElement对象组成了一个JavaScript的对象树;JavaScript的对象树就是大名鼎鼎的虚拟DOM(Virtual DOM);如何查看ReactElement的树结构呢?我们可以将之前的jsx返回结果进行打印;注意下面代码中我打jsx的打印;而ReactElement最终形成的树结构就是Virtual DOM;

{
    $$typeof: Symbol(react.element),
    key: null,
    props: {children: Array(3)},
    ref: null,
    type: "div",
    _owner: FiberNode {tag: 1, key: null, stateNode: App, elementType: ƒ, type: ƒ,},
    _store: {validated: false},
}

为什么使用虚拟DOM

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

声明式编程

虚拟DOM帮助我们从命令式编程转到了声明式编程的模式。React官方的说法:Virtual DOM 是一种编程理念。在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象。我们可以通过ReactDOM.render让虚拟DOM 和真实DOM同步起来,这个过程中叫做协调(Reconciliation);这种编程的方式赋予了React声明式的API:你只需要告诉React希望让UI是什么状态;React来确保DOM和这些状态是匹配的;你不需要直接进行DOM操作,只可以从手动更改DOM、属性操作、事件处理中解放出来;