react 学习笔记

组件

添加组件

<!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
<script type="text/babel">
    // 方式一
    // 这个构造函数,就相当于一个 组件 也可以直接引入处理
    function Hello() {
        return (
            <div>
                <h3>这是 Hello组件 中定义的元素</h3>
            </div>
            );
    }

    // 方式二
    // 使用 class 创建的类,通过 extends 关键字,继承 `React.Component` 之后,这个类,就是一个组件的模板了。
    // 如果想要引用这个组件,可以把类的名称以**标签的形式**,导入到 JSX 中使用。
    class Hello2 extends React.Component {
        // 在 class 实现的组件内部,必须定义一个 render 函数
        render() {
            // 在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null
            return (
                    <div>
                        <h3>这是使用 class 类创建的组件 </h3>
                    </div>
            );
        }
    }

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

需要注意的是

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

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

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

组件传值

<!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
<script type="text/babel">
    // 父组件中的数据
    var person = {
        name: "xx",
        age: 27,
        gender: "男",
        address: "xx"
    };

    // 方式一
    // 在子组件中,如果想要使用外部传递过来的数据,必须显示的在 构造函数参数列表中,定义 props 属性来接收
    // 通过 props 得到的任何数据都是只读的,不能重新赋值
    function Hello(props) {
        return (
                <div>
                    <h3>这是 Hello子组件 中定义的元素 {props.name}</h3>
                </div>
        );
    }

    // 方式二
    // 使用 class 创建的类,通过 extends 关键字,继承 `React.Component` 之后,这个类,就是一个组件的模板了。
    // 如果想要引用这个组件,可以把类的名称以**标签的形式**,导入到 JSX 中使用。
    class Hello2 extends React.Component {
        constructor(props) {
            super(props);
            console.log(props.name);

            // 注意:`this.state` 是固定写法,表示当前组件实例的私有数据对象,就好比 vue 中,组件实例身上的 data(){ return {} } 函数
            // 如果想要使用 组件中 state 上的数据,直接通过 this.state.*** 来访问即可
            this.state = {
                msg: "这是 Hello2 组件的私有msg数据",
                info: "xx"
            };
        }
        //无constructor 则直接定义
        //   state = {
        //       msg: 'hello',
        //   }
        // 在 class 实现的组件内部,必须定义一个 render 函数
        render() {
            // 在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null
            return (
                    <div>
                        <h3>这是使用 class 类创建的组件 {this.props.name}</h3>
                    </div>
            );
        }
    }

    ReactDOM.render(
            //注意:这里的 ...Obj 语法,是 ES6中的属性扩散,表示:把这个对象上的所有属性,展开了,放到这个位置,类似name={person.name} age={person.age}
            <div>
                <Hello {...person}> </Hello>
            </div>,
            document.getElementById("app")
    );
</script>

对比

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

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

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

本质区别

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

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

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

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

生命周期

1、组件创建阶段

组件创建阶段的生命周期函数,有一个显著的特点:创建阶段的生命周期函数,在组件的一辈子中,只执行一次。

  • getDefaultProps

初始化 props 属性默认值。

  • getInitialState

初始化组件的私有数据。因为 state 是定义在组件的 constructor 构造器当中的,只要new 了 class类,必然会调用 constructor构造器。

  • componentWillMount()

组件将要被挂载。此时还没有开始渲染虚拟DOM。

在这个阶段,不能去操作DOM元素,但可以操作属性、状态、function。相当于 Vue 中的Create()函数。

  • render()

第一次开始渲染真正的虚拟DOM。当render执行完,内存中就有了完整的虚拟DOM了。

意思是,此时,虚拟DOM在内存中创建好了,但是还没有挂在到页面上。

在这个函数内部,不能去操作DOM元素,因为还没return之前,虚拟DOM还没有创建;当return执行完毕后,虚拟DOM就创建好了,但是还没有挂在到页面上。

  • componentDidMount()

当组件(虚拟DOM)挂载到页面之后,会进入这个生命周期函数

只要进入到这个生命周期函数,则必然说明,页面上已经有可见的DOM元素了。此时,组件已经显示到了页面上,state上的数据、内存中的虚拟DOM、以及浏览器中的页面,已经完全保持一致了。

当这个方法执行完,组件就进入都了 运行中 的状态。所以说,componentDidMount 是创建阶段的最后一个函数。

在这个函数中,我们可以放心的去 操作 页面上你需要使用的 DOM 元素了。如果我们想操作DOM元素,最早只能在 componentDidMount 中进行。相当于 Vue 中的 mounted() 函数

2、组件运行阶段

有一个显著的特点,根据组件的state和props的改变,有选择性的触发0次或多次。

  • componentWillReceiveProps()

组件将要接收新属性。只有当父组件中,通过某些事件,重新修改了 传递给 子组件的 props 数据之后,才会触发这个钩子函数。

  • shouldComponentUpdate()

判断组件是否需要被更新。此时,组件尚未被更新,但是,state 和 props 肯定是最新的。

  • componentWillUpdate()

组件将要被更新。此时,组件还没有被更新,在进入到这个生命周期函数的时候,内存中的虚拟DOM还是旧的,页面上的 DOM 元素也是旧的。(也就是说,此时操作的是旧的 DOM元素)

  • render

此时,又要根据最新的 state 和 props,重新渲染一棵内存中的 虚拟DOM树。当 render 调用完毕,内存中的旧DOM树,已经被新DOM树替换了!此时,虚拟DOM树已经和组件的 state 保持一致了,都是最新的;但是页面还是旧的。

  • componentDidUpdate

此时,组件完成更新,页面被重新渲染。此时,state、虚拟DOM 和 页面已经完全保持同步。

3、组件销毁阶段

一辈子只执行一次。

  • componentWillUnmount: 组件将要被卸载。此时组件还可以正常使用。

绑定this并给函数传参

  • 箭头函数 或通过 bind() 绑定this
import React from "react";

export default class MyComponent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      msg: "这是 MyComponent 组件 默认的msg"
    };
  }

  render() {
    return (
      <div>
        <h1>绑定This并传参</h1>
        <input
          type="button"
          value="绑定this并传参"
          onClick={() => {
            this.changeMsg3("千古3", "壹号3");
          }}
        />
        <h3>{this.state.msg}</h3>
      </div>
    );
  }
  
  changeMsg = () => {
      console.log(this); // 打印结果:MyComponent 组件
        this.setState({
          msg: "设置 msg 为新的值"
      });
  }
    
  changeMsg3 = (arg1, arg2) => {
    // console.log(this);
    // 注意:这里的方式,是一个普通方法,因此,在触发的时候,这里的 this 是 undefined
    this.setState({
      msg: "绑定this并传参的方式3:" + arg1 + arg2
    });
  };
}

单项数据绑定

在 Vue 中,可以通过 v-model 指令来实现双向数据绑定。但是,在 React 中并没有指令的概念,而且 React 默认不支持 双向数据绑定

React 只支持,把数据从 state 上传输到 页面,但是,无法自动实现数据从 页面 传输到 state 中 进行保存。

React中,只支持单项数据绑定,不支持双向数据绑定

通过onChange方法,实现双向数据绑定表单

如果针对 表单元素做 value 属性绑定,那么,必须同时为 表单元素 绑定 readOnly, 或者提供 onChange 事件:

  • 如果是绑定readOnly,表示这个元素只读,不能被修改。此时,控制台就不会弹出警告了。

  • 如果是绑定onChange,表示这个元素的值可以被修改,但是,要自己定义修改的逻辑。

绑定 onChange 的举例如下:(通过onChange方法,实现双向数据绑定)

(1)index.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <!-- 容器,通过 React 渲染得到的 虚拟DOM,会呈现到这个位置 -->
  <div id="app"></div>
</body>

</html>

(2)main.js:

// JS打包入口文件
// 1. 导入包
import React from "react";
import ReactDOM from "react-dom";

// 导入组件
import MyComponent from "./components/MyComponent.jsx";

// 使用 render 函数渲染 虚拟DOM
ReactDOM.render(
  <div>
    <MyComponent></MyComponent>
  </div>,
  document.getElementById("app")
);

(3)components/MyComponent.jsx

import React from "react";

export default class MyComponent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      msg: "这是组件 默认的msg"
    };
  }

  render() {
    return (
      <div>
        <h1>呵呵哒</h1>
        <input
          type="text" value={this.state.msg} onChange={this.txtChanged} ref="txt" />
        <h3>{"实时显示msg中的内容:" + this.state.msg}</h3>
      </div>
    );
  }

  // 为 文本框 绑定 txtChanged 事件
  txtChanged = (e) => {
    // 获取 <input> 文本框中 文本的3种方式:
    //  方式一:使用 document.getElementById

    //  方式二:使用 ref
    // console.log(this.refs.txt.value);

    //  方式三:使用 事件对象的 参数 e 来拿
    // 此时,e.target 就表示触发 这个事件的 事件源对象,得到的是一个原生的JS DOM 对象。在这个案例里,e.target就是指文本框
    // console.log(e.target.value);
    this.setState({
      msg: e.target.value
    });
  };
}