02-JSX语法介绍

JSX介绍

JSX的引入

如果直接让用户通过 JS 代码手动创建DOM元素,肯定是非常麻烦的。

于是,React 官方就提出了一套 JSX 语法规范,能够让我们在 JS 文件中,书写类似于 HTML 那样的代码,快速定义虚拟DOM结构。

JSX的全称

JSX:JavaScript XML,一种类似于XML的JS扩展语法。也可以理解成:符合 XML 规范的 JS 语法。

需要注意的是,哪怕你在 JS 中写的是 JSX 语法(即JSX这样的标签),但是,JSX内部在运行的时候,并不是直接把 我们的 HTML 标签渲染到页面上;而是先把 类似于HTML 这样的标签代码,转换成 React.createElement 这样的JS代码,再渲染到页面中。

从这一点我们可以看出,JSX是一个对程序员友好的语法糖。

JSX语法的本质:以 React.createElement 的形式来实现的,并没有直接把 用户写的 HTML代码,渲染到页面上。

babel转换工具

如果要直接使用 JSX 语法,需要先安装相关的 语法转换工具:

  1. 运行 cnpm i babel-preset-react -D

这个babel包的作用是:将 JSX语法 转换为 JS语法。

安装完成后,就可以开始使用JSX语法了。

完整代码举例:

  1. <!DOCTYPE html>
  2. <html lang="">
  3. <head>
  4. <meta />
  5. <meta />
  6. <meta />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <!-- 引入React相关的js库 -->
  11. <script type="text/javascript" src="./libs/react.js"></script>
  12. <script type="text/javascript" src="./libs/react-dom.js"></script>
  13. <script type="text/javascript" src="./libs/babel.min.js"></script>
  14. <div id="app"></div>
  15. <!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
  16. <script type="text/babel">
  17. //页面中的真实容器元素
  18. var containDiv = document.getElementById("app");
  19. //1、使用JSX语法 创建虚拟DOM对象
  20. var vDom = (
  21. <div>
  22. Hello, React!
  23. <h2>这是标题</h2>
  24. </div>
  25. );
  26. //2、渲染虚拟DOM对象(将虚拟DOM对象渲染到页面元素中)
  27. ReactDOM.render(vDom, containDiv); // 参数1:虚拟DOM对象;参数2:页面中的容器
  28. </script>
  29. </body>
  30. </html>

JSX的基本语法

(1)在 JSX内部 写 JS代码:如果要在 JSX 语法内部,书写 JS 代码,那么,所有的JS代码必须写到 {} 的内部。在{}内部,可以写任何符合JS规范的代码。

例如:

  1. var myTitle = '这是使用变量定义的 tilte 值'
  2. // 使用JSX语法 创建虚拟DOM对象
  3. var vDom = (
  4. <div>
  5. Hello, React!
  6. <h2 title={myTitle + 'vae'}>这是标题</h2>
  7. </div>
  8. );

(2)当编译引擎在编译JSX代码的时候,如果遇到了<,会把它当作 HTML代码 去编译;如果遇到了 {}, 会把方括号里面的代码当作 普通JS代码 去编译。

(3)在JSX中,如果要为元素添加class属性,则必须写成className,因为 class在ES6中是一个关键字;和class类似,label标签的 for 属性需要替换为 htmlFor

代码举例:

  1. // 使用JSX语法 创建虚拟DOM对象
  2. var vDom = (
  3. <div>
  4. Hello, React!
  5. <p className="qianguyihao">千古壹号</p>
  6. <label htmlFor="" />
  7. </div>
  8. );

(4)在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹。

(5)如果要写注释,注释必须放到 {} 内部。例如:

  1. // 使用JSX语法 创建虚拟DOM对象
  2. var vDom = (
  3. // 这一行是注释
  4. <div>
  5. Hello, React!
  6. <p className="qianguyihao">千古壹号</p>
  7. {/*这一行也是注释 */}
  8. </div>
  9. );

最后,再举个例子:

  1. <!DOCTYPE html>
  2. <html lang="">
  3. <head>
  4. <meta />
  5. <meta />
  6. <meta />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <!-- 引入React相关的js库 -->
  11. <script type="text/javascript" src="./libs/react.js"></script>
  12. <script type="text/javascript" src="./libs/react-dom.js"></script>
  13. <script type="text/javascript" src="./libs/babel.min.js"></script>
  14. <div id="app"></div>
  15. <!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
  16. <script type="text/babel">
  17. //页面中的真实容器元素
  18. var containDiv = document.getElementById("app");
  19. var arr = []
  20. for (var i = 0; i < 6; i++) {
  21. var p = <p className="myp" key={i}>这个是p标签</p> // 注意这个地方的写法: key = {i}
  22. arr.push(p)
  23. }
  24. //1、使用JSX语法 创建虚拟DOM对象
  25. var vDom = (
  26. <div>
  27. Hello, React!
  28. {arr}
  29. </div>
  30. );
  31. //2、渲染虚拟DOM对象
  32. ReactDOM.render(vDom, containDiv); // 参数1:虚拟DOM对象;参数2:页面中的容器
  33. </script>
  34. </body>
  35. </html>

运行结果:

20190210_1501.png

创建组件的第一种方式

创建组件

在React中,构造函数就是一个最基本的组件。

如果想要把组件放到页面中,可以把构造函数的名称当作组件的名称,以 HTML标签形式引入页面中即可。

举例:

  1. <!DOCTYPE html>
  2. <html lang="">
  3. <head>
  4. <meta />
  5. <meta />
  6. <meta />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <!-- 引入React相关的js库 -->
  11. <script type="text/javascript" src="./libs/react.js"></script>
  12. <script type="text/javascript" src="./libs/react-dom.js"></script>
  13. <script type="text/javascript" src="./libs/babel.min.js"></script>
  14. <div id="app"></div>
  15. <!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
  16. <script type="text/babel">
  17. // 这个构造函数,就相当于一个 组件
  18. function Hello() {
  19. return (
  20. <div>
  21. <h3>这是 Hello组件 中定义的元素</h3>
  22. </div>
  23. );
  24. }
  25. ReactDOM.render(
  26. <div>
  27. <Hello> </Hello>
  28. </div>,
  29. document.getElementById("app")
  30. );
  31. </script>
  32. </body>
  33. </html>

运行结果:

20190210_1510.png

需要注意的是

React在解析所有标签的时候,是以标签的首字母来区分的:如果标签的首字母是小写,就按照普通的 HTML 标签来解析;如果首字母是大写,则按照 组件的形式来解析。

比如上方代码中,如果把大写的 Hello 改成小写的 hello,运行会报错,无法看到预期的结果。

结论:组件的首字母必须大写。

父组件传值给子组件

代码举例:

  1. <!DOCTYPE html>
  2. <html lang="">
  3. <head>
  4. <meta />
  5. <meta />
  6. <meta />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <!-- 引入React相关的js库 -->
  11. <script type="text/javascript" src="./libs/react.js"></script>
  12. <script type="text/javascript" src="./libs/react-dom.js"></script>
  13. <script type="text/javascript" src="./libs/babel.min.js"></script>
  14. <div id="app"></div>
  15. <!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
  16. <script type="text/babel">
  17. // 父组件中的数据
  18. var person = {
  19. name: "qianguyihao",
  20. age: 27,
  21. gender: "男",
  22. address: "深圳"
  23. };
  24. // 在子组件中,如果想要使用外部传递过来的数据,必须显示的在 构造函数参数列表中,定义 props 属性来接收
  25. // 通过 props 得到的任何数据都是只读的,不能重新赋值
  26. function Hello(props) {
  27. return (
  28. <div>
  29. <h3>这是 Hello子组件 中定义的元素: {props.name}</h3>
  30. </div>
  31. );
  32. }
  33. ReactDOM.render(
  34. <!-- 注意:这里的 ...Obj 语法,是 ES6中的属性扩散,表示:把这个对象上的所有属性,展开了,放到这个位置 -->
  35. <div>
  36. <Hello {...person}> </Hello>
  37. </div>,
  38. document.getElementById("app")
  39. );
  40. </script>
  41. </body>
  42. </html>

上方代码中,我们是想把整个person对象传递给子组件,所以采用了...Obj 语法语法。传递给子组件后,子组件获取的数据仅仅只是可读的。

class 关键字的介绍

面向对象语言的三个特性:封装、继承、多态。多态 和 接口、虚拟方法有关。

class的基本用法:使用class创建对象

myclass.js:

  1. // 以前学习的:使用构造函数创建对象
  2. function Person(name, age) {
  3. this.name = name;
  4. this.age = age;
  5. }
  6. Person.prototype.say = function() {
  7. console.log("呵呵哒");
  8. };
  9. Person.info = 123;
  10. var p1 = new Person("zs", 20);
  11. // 本次需要学习的:class 后面跟上类名,类名后面,不需要加 () ,直接上 {}
  12. class Per {
  13. // 在每个class类内部,都有一个 constructor 构造器, 如果没有显示定义 构造器,那么类内部默认都有个看不见的 constructor
  14. // constructor 的作用,就好比 咱们之前的 function Person(){ }
  15. // 每当,使用 new 关键字创建 class 类实例的时候,必然会优先调用 constructor 构造器
  16. // constructor(){}
  17. constructor(name, age) {
  18. this.name = name;
  19. this.age = age;
  20. }
  21. // 这是实例方法,必须通过 new 出来的对象调用
  22. say() {
  23. console.log("ok a ");
  24. }
  25. static info = 123;
  26. static sayHello() {
  27. console.log("这是静态方法");
  28. }
  29. }
  30. var p2 = new Per("壹号", 26);
  31. console.log(p2);
  32. console.log(Per.info);
  33. console.log(Per.sayHello());

使用 class 实现 JS 中的继承

myclass2.js:

  1. class Person {
  2. constructor(name, age) {
  3. console.log(3);
  4. this.name = name;
  5. this.age = age;
  6. }
  7. say() {
  8. console.log("这是 Person中的 say 方法");
  9. }
  10. static info = 123;
  11. }
  12. // 使用 extends 实现继承,extends的前面的是子类,后面的是父类
  13. class Chinese extends Person {
  14. constructor(name, age, color, language) {
  15. console.log(1);
  16. // 注意: 当使用 extends 关键字实现了继承, 子类的 constructor 构造函数中,必须显示调用 super() 方法,这个 super 表示父类中 constructor 的引用
  17. super(name, age);
  18. this.color = color;
  19. this.language = language;
  20. console.log(2);
  21. }
  22. }
  23. var c1 = new Chinese("张三", 22, "yellow", "汉语");
  24. console.log(c1);
  25. // 父类中任何东西,子类都能继承到
  26. c1.say();

注意上方 constructor处的注释:当使用 extends 关键字实现了继承, 子类的 constructor 构造函数中,必须显示调用 super() 方法,这个 super 表示父类中 constructor 的引用。也就是说,在子类当中,要么不写 constructor,如果写了 constructor,就一定要把 super()也加上。

为啥我们要引入 class这个功能?就是因为, class里,永远都存在着一个 constructor。我们可以利用 constructor做很多事情。

创建组件的第二种方式:使用 class 关键字

使用 class 创建的类,通过 extends 关键字,继承 React.Component 之后,这个类,就是一个组件的模板了。如果想要引用这个组件,可以把类的名称以标签的形式,导入到 JSX 中使用。

在 class 实现的组件内部,必须定义一个 render 函数。在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null。

代码举例

index.html:

  1. <!DOCTYPE html>
  2. <html lang="">
  3. <head>
  4. <meta />
  5. <meta />
  6. <meta />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <!-- 引入React相关的js库 -->
  11. <script type="text/javascript" src="./libs/react.js"></script>
  12. <script type="text/javascript" src="./libs/react-dom.js"></script>
  13. <script type="text/javascript" src="./libs/babel.min.js"></script>
  14. <div id="app"></div>
  15. <!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
  16. <script type="text/babel">
  17. // 使用 class 创建的类,通过 extends 关键字,继承 `React.Component` 之后,这个类,就是一个组件的模板了。
  18. // 如果想要引用这个组件,可以把类的名称以**标签的形式**,导入到 JSX 中使用。
  19. class Hello2 extends React.Component {
  20. // 在 class 实现的组件内部,必须定义一个 render 函数
  21. render() {
  22. // 在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null
  23. return (
  24. <div>
  25. <h3>这是使用 class 类创建的组件 </h3>
  26. </div>
  27. );
  28. }
  29. }
  30. ReactDOM.render(
  31. <div>
  32. <Hello2> </Hello2>
  33. </div>,
  34. document.getElementById("app")
  35. );
  36. </script>
  37. </body>
  38. </html>

父组件传值给子组件

代码举例:

index.html:

  1. <!DOCTYPE html>
  2. <html lang="">
  3. <head>
  4. <meta />
  5. <meta />
  6. <meta />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <!-- 引入React相关的js库 -->
  11. <script type="text/javascript" src="./libs/react.js"></script>
  12. <script type="text/javascript" src="./libs/react-dom.js"></script>
  13. <script type="text/javascript" src="./libs/babel.min.js"></script>
  14. <div id="app"></div>
  15. <!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
  16. <script type="text/babel">
  17. // 使用 class 创建的类,通过 extends 关键字,继承 `React.Component` 之后,这个类,就是一个组件的模板了。
  18. // 如果想要引用这个组件,可以把类的名称以**标签的形式**,导入到 JSX 中使用。
  19. class Hello2 extends React.Component {
  20. constructor(props) {
  21. super(props);
  22. console.log(props.name);
  23. // 注意:`this.state` 是固定写法,表示当前组件实例的私有数据对象,就好比 vue 中,组件实例身上的 data(){ return {} } 函数
  24. // 如果想要使用 组件中 state 上的数据,直接通过 this.state.*** 来访问即可
  25. this.state = {
  26. msg: "这是 Hello2 组件的私有msg数据",
  27. info: "永不止步"
  28. };
  29. }
  30. // 在 class 实现的组件内部,必须定义一个 render 函数
  31. render() {
  32. // 在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null
  33. return (
  34. <div>
  35. <h3>这是使用 class 类创建的组件 </h3>
  36. </div>
  37. );
  38. }
  39. }
  40. ReactDOM.render(
  41. <div>
  42. <Hello2 name="qianguyihao"> </Hello2>
  43. </div>,
  44. document.getElementById("app")
  45. );
  46. </script>
  47. </body>
  48. </html>

方式一和方式二的对比

上面的内容里,我们使用了两种方式创建组件。这两种方式,有着本质的区别,我们来对比一下。

对比

  • 方式一:通过 function构造函数 创建组件。内部没有 state 私有数据,只有 一个 props 来接收外界传递过来的数据。

  • 方式二:通过 class 创建子组件。内部除了有 this.props 这个只读属性之外,还有一个专门用于 存放自己私有数据的 this.state 属性,这个 state 是可读可写的。

基于上面的区别,我们可以为这两种创建组件的方式下定义: 使用 function 创建的组件,叫做【无状态组件】;使用 class 创建的组件,叫做【有状态组件】。

本质区别

有状态组件和无状态组件,最本质的区别,就是有无 state 属性。同时, class 创建的组件,有自己的生命周期函数,但是,function 创建的 组件,没有自己的生命周期函数。

什么时候使用 有状态组件,什么时候使用无状态组件

  • (1)如果一个组件需要存放自己的私有数据,或者需要在组件的不同阶段执行不同的业务逻辑,此时,非常适合用 class 创建出来的有状态组件。

  • (2)如果一个组件,只需要根据外界传递过来的 props,渲染固定的页面结构即可的话,此时,非常适合使用 function 创建出来的无状态组件。(使用无状态组件的小小好处: 由于剔除了组件的生命周期,所以,运行速度会相对快一点点)。