React学习笔记(二) -- 面向组件编程

474 阅读11分钟

GitHub:React 源码+笔记

一、 基本概念:

1. 模块

1. 理解:向外提供特定功能的js程序, 一般就是一个js文件

2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。

3. 作用:复用js, 简化js的编写, 提高js运行效率

2. 组件

1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)

2. 为什么要用组件: 一个界面的功能更复杂

3. 作用:复用编码, 简化项目编码, 提高运行效率

3. 模块化

当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

4. 组件化

当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

二、组件的使用

当应用是以多组件的方式实现,这个应用就是一个组件化的应用

注意:

  1. 组件名必须是首字母大写
  2. 虚拟DOM元素只能有一个根元素
  3. 虚拟DOM元素必须有结束标签 < />

渲染类组件标签的基本流程

  1. React 内部会创建组件实例对象
  2. 调用render()得到虚拟 DOM ,并解析为真实 DOM
  3. 插入到指定的页面元素内部

1. 函数式组件(函数名就是组件名)

函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。 函数组件有自己的特点(当然,后面我们会讲hooks,就不一样了):

  • 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
  • 没有this(组件实例);
  • 没有内部状态(state); 我们来定义一个函数组件
//1.先创建函数,函数可以有参数,也可以没有,但是必须要有返回值 返回一个虚拟DOM
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
//2.进行渲染
ReactDOM.Render(<Welcom name = "ljc" />,document.getElementById("div"));

上面的代码经历了以下几步

  1. 我们调用 ReactDOM.render() 函数,并传入 <Welcome name="ljc" /> 作为参数。
  2. React 调用 Welcome 组件,并将 {name: 'ljc'} 作为 props 传入。
  3. Welcome 组件将 Hello, ljc 元素作为返回值。
  4. React DOM 将 DOM 高效地更新为 Hello,ljc

实例

点击跳转:严格模式

<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
       //1.创建函数式组件
     function MyComponent() {
          console.log('this:', this); //此处的this是undefined,
//因为babel编译后开启了严格模式, 严格模式禁止将自定义里的函数this指向window.
          return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
      }
       //2.渲染组件到页面(组件名与函数名相对应,组件标签必须闭合且首字母必须大写)
    ReactDOM.render(<MyComponent />, document.getElementById('test'))

执行了ReactDOM.render(.......)之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。

2. 类式组件(类名就是组件名)

类的基础知识

<script type="text/javascript">
 /* 
    总结:
    1.类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
    2.如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
    3.类中所定义的方法,都放在了类的原型对象上,供实例去使用。
    4.类中你定义的方法已经在局部开好了严格模式
  */
//创建一个Person类
class Person {
        //构造器方法
        constructor(name, age) {
                //构造器中的this是谁?—— 类的实例对象
                this.name = name
                this.age = age
        }
        //一般方法
        speak() {
                //speak方法放在了哪里?—— 类的原型对象上,供实例使用
                //通过Person实例调用speak时,speak中的this就是Person实例
                console.log(`我叫${this.name},我年龄是${this.age}`);
        }
}
const p1 = new Person('tom', 18)
const p2 = new Person('jack', 19)
console.log(p1);
console.log(p2);
p1.speak()
p2.speak()
//创建一个Student类,继承于Person类
class Student extends Person {
        constructor(name, age, grade) {
                super(name, age)
                this.grade = grade
                this.school = '尚硅谷'
        }
        //重写从父类继承过来的方法
        speak() {
            console.log(`我叫${this.name},我年龄是${this.age},我读的是${this.grade}年级`);
            this.study()
        }
        study() {
                //study方法放在了哪里?——类的原型对象上,供实例使用
                //通过Student实例调用study时,study中的this就是Student实例
                console.log('我很努力的学习');
        }
}
const s1 = new Student('zgc', 21, '大四')
console.log(s1);
s1.speak()
s1.study()
class Car {
        constructor(name, price) {
            this.name = name
            this.price = price
            // this.wheel = 4
        }
        //类中可以直接写赋值语句,如下代码的含义是:给Car的实例对象添加一个属性,名为a,值为1
        a = 1
        wheel = 4
        static demo = 100
}
const c1 = new Car('奔驰c63', 199)
console.log(c1);
console.log(Car.demo);//100
console.log(c1.demo);//undefined,static 定义静态方法和属性,声明的值只有自身才能够调用
</script>

类组件的定义有如下要求

  • 组件的名称是大写字符开头(无论类组件还是函数组件)
  • 类组件需要继承自 React.Component
  • 类组件必须实现render函数 使用class定义一个组件
  • constructor是可选的,我们通常在constructor中初始化一些数据;
  • this.state中维护的就是我们组件内部的数据;
  • render() 方法是 class 组件中唯一必须实现的方法 当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一
  • React 元素( 通常通过 JSX 创建,如<div/>会被 React 渲染为 DOM 节点,MyComponent 会被 React 渲染为自定义组件;无论是<div/>还是 MyComponent均为 React 元素。)
  • 数组或 fragments(片段):使得 render 方法可以返回多个元素。
  • Portals:可以渲染子节点到不同的 DOM 子树中。
  • 字符串或数值类型:它们在 DOM 中会被渲染为文本节点
  • 布尔类型或 null:什么都不渲染。

实例

<!-- 准备好一个“容器” -->
<div id="test"></div>

<script type="text/babel">
    //1.创建类式组件
    class MyComponent extends React.Component {
         render(){
   //render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
   //render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象
            console.log('render中的this:',this);
            return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
        }
    }
     //2.渲染组件到页面
        ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>


执行了ReactDOM.render(.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用原型上的render方法
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中

三、组件实例的三大属性--用class创建组件实例

1. state

React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。

React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。

简单的说就是组件的状态,也就是该组件所存储的数据

类式组件中的使用

<div id="test"></div>

<script type="text/babel">
//1.创建组件
class Weather extends React.Component {

    //构造器调用几次? ———— 1次
    constructor(props) {
    //构造器中的this固定为组件实例对象
        super(props)
          //初始化状态
        this.state = { isHot: false, wind: '微风' }
          //this实例沿着原型链找到changeWeather,用bind将原型上的changeWeather
          //(即下面写的changeWeather函数)改变了this指向,赋给组件实例自身
          //(会有两个changeWeather,一个在原型,一个在自身)
        this.changeWeather = this.changeWeather.bind(this)
    }

      //render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
    render() {
    //render中的this也是组件实例对象,因为 ReactDOM.render new出来该类的实例,并通过该实例调用原型上的render方法
          //解构赋值读取状态
        const { isHot, wind } = this.state
          //绑定事件用驼峰命名,{}中的自定义函数不要加()
          //这时候就会首先调用实例自身的changeWeather
        return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
    }

           //changeWeather调用几次? ———— 点几次调几次
        changeWeather() {
            //changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
            //由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
            //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
          console.log(this);

            //获取原来的isHot值
          const isHot = this.state.isHot
            //严重注意:状态(state)必须通过setState进行更新,且更新是一种合并,不是替换,state中其他属性不会被更新。
          this.setState({ isHot: !isHot })
            //严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
            //this.state.isHot = !isHot //这是错误的写法
        }
}
//2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
</script>
  • changeWeather 作为 onClick 的回调,是用this从实例对象直接的原型链中找到changeWeather,然后再赋值给onClick,点击之后直接从堆中将函数调用,根本不是从实例对象中调用。类中的方法默认开启了局部的严格模式,此时的this是undefined。
  • bind: 做了两件事情 ---- 生成新的函数并且改变其this为Weather的实例对象,this.changeWeather是原型上的方法,通过bind改变this之后生成新的方法放在了实例自身上,导致了实例中也有changeWeather这个方法,这样就能进行调用了。

image.png

思考: 那为什么 render 函数里面的 this 指向的不是 undefined

解释:JSX代码经过 Babel 编译之后变成 React 的表达式,这个表达式在render函数被调用的时候通过 React 的 createElement 方法生成一个element,在这个方法中,this指向了被创建的类(也就是相应的React组件)。

简写

<!-- 准备好一个“容器” -->
<div id="test"></div>

<script type="text/babel">
      //1.创建组件
    class Weather extends React.Component {
      //初始化状态,类中可以直接写赋值语句,如下代码的含义是:给Weather的实例对象添加一个属性state
    state = { isHot: false, wind: '微风' }

    render() {
        const { isHot, wind } = this.state
        return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
    }

      //自定义方法————要用赋值语句的形式+箭头函数の形式
      // 这样就不需要在constructor中改变this指向了,这时changeWeather在组件实例自身,而不是再其原型链中
    changeWeather = () => {
            const isHot = this.state.isHot
            this.setState({ isHot: !isHot })
    }
}
   //2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
</script>

在优化过程中遇到的问题

  1. 组件中的 render 方法中的 this 为组件实例对象

  2. 组件自定义方法中由于开启了严格模式,this 指向 undefined 如何解决

    1. 通过 bind 改变 this 指向

    2. 推荐采用箭头函数+赋值语句的形式,这样就不需要在constructor中改变this指向,

    3. state 数据不能直接修改或者更新,需要通过setState()修改

这玩意底层不简单,this的指向真的需要好好学习

2. props

state不同,state是组件自身的状态,而props则是外部传入的数据

  1. 每个组件对象都会有props(properties的简写)属性
  2. 组件标签的所有属性都保存在props中
  3.  通过标签属性从组件外向组件内传递变化的数据
  4.  注意: 组件内部不要修改props数据

类式组件中使用

<script type="text/babel">
    //创建组件
    class Person extends React.Component {
        render() {
                // 解构赋值,从props取出值,简化写法
            const { name, age, sex } = this.props
            return (
                <ul>
                    <li>姓名:{this.props.name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age + 1}</li>
                </ul>
            )
        }
    }
    //渲染组件到页面
    ReactDOM.render(<Person name="jerry" age={19} sex="男" />, document.getElementById('test1'))
     //组件标签中引号不能去掉,会报错,想将其表示为数字则用{}
    ReactDOM.render(<Person name="tom" age='18' sex="女" />, document.getElementById('test2'))
    
    const p = { name: '老刘', age: 18, sex: '女' }
    //  ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
    // 简化批量接收的数据
    ReactDOM.render(<Person {...p} />, document.getElementById('test3'))
</script>

image.png

使用 PropTypes 进行类型检查

<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>

<script type="text/babel">
        //创建组件
    class Person extends React.Component {
        render() {
            const { name, age, sex } = this.props
            //props是只读的
            //this.props.name = 'jack' //此行代码会报错,因为props是只读的
            return (
                <ul>
                    <li>姓名:{this.props.name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age + 1}</li>
                </ul>
            )
        }
    }
    //对标签属性进行类型、必要性的限制
    Person.propTypes = {
        name: PropTypes.string.isRequired, //限制name必传,且为字符串
        sex: PropTypes.string,//限制sex为字符串
        age: PropTypes.number,//限制age为数值
        speak: PropTypes.func,//限制speak为函数
    }
    //指定默认标签属性值
    Person.defaultProps = {
        sex: '男',//sex默认值为男
        age: 18 //age默认值为18
    }
    //渲染组件到页面
    ReactDOM.render(<Person name='jack' speak={speak} />, document.getElementById('test1'))
    ReactDOM.render(<Person name="tom" age={18} sex="女" />, document.getElementById('test2'))

    const p = { name: '老刘', age: 18, sex: '女' }
    // ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
    ReactDOM.render(<Person {...p} />, document.getElementById('test3'))

    function speak() {
            console.log('我说话了');
    }
</script>

简写(将对props的规范和默认值写在类式组件之内)

<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>

<script type="text/babel">
    //创建组件
    class Person extends React.Component{

        constructor(props){
//构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props
            // console.log(props);
            super(props)
            console.log('constructor',this.props);
        }

          //对标签属性进行类型、必要性的限制
        static propTypes = {
            name:PropTypes.string.isRequired, //限制name必传,且为字符串
            sex:PropTypes.string,//限制sex为字符串
            age:PropTypes.number,//限制age为数值
        }

        //指定默认标签属性值
        static defaultProps = {
            sex:'男',//sex默认值为男
            age:18 //age默认值为18
        }

        render(){
              // console.log(this);
            const {name,age,sex} = this.props
              //props是只读的
              //this.props.name = 'jack' //此行代码会报错,因为props是只读的
            return (
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age+1}</li>
                </ul>
            )
        }
    }

    //渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
</script>

在使用的时候可以通过 this.props来获取值 类式组件的 props:

  1. 通过在组件标签上传递值,在组件中就可以获取到所传递的值
  2. 在构造器里的props参数里可以获取到 props
  3. 可以分别设置 propTypesdefaultProps 两个属性来分别操作 props的规范和默认值,两者可以直接添加在类式组件的原型对象上(所以需要添加 static),也可以写在组件外侧,需要用script标签引入一个第三方库,或者yarn add prop-types
  4. 同时可以通过...运算符来简化

函数式组件中的使用

函数在使用props的时候,是作为参数进行使用的(props)

函数组件的 props定义:

  1. 在组件标签中传递 props的值
  2. 组件函数的参数为 props
  3. props的限制和默认值同样设置在原型对象上
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>

<script type="text/babel">
    //创建组件
    function Person(props) {
            //结构赋值
            const { name, age, sex } = props
            return (
                    <ul>
                        <li>姓名:{props.name}</li>
                        <li>性别:{props.sex}</li>
                        <li>年龄:{age}</li>
                    </ul>
            )
    }
    //对标签属性进行类型,必要性的限制
    Person.propTypes = {
            name: PropTypes.string.isRequired, //限制name必传,且为字符串
            sex: PropTypes.string,//限制sex为字符串
            age: PropTypes.number,//限制age为数值
    }

    //指定默认标签属性值
    Person.defaultProps = {
            sex: '男',//sex默认值为男
            age: 18 //age默认值为18
    }
    //渲染组件到页面
ReactDOM.render(<Person name="jack"/>, document.getElementById('test1'))
</script>

子组件给父组件传值

某些情况,我们也需要子组件向父组件传递消息:

  • 在vue中是通过自定义事件来完成的;
  • 在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可; 我们这里来完成一个案例

将计数器案例进行拆解;将按钮封装到子组件中:CounterButton; CounterButton发生点击事件,将内容传递到父组件中,修改counter的值

import React, { Component } from "react";

// 子
class CounterButton extends Component {
  render() {
    const { onClick } = this.props;

    return <button onClick={() => onClick(1)}>+1</button>;
  }
}

// 父
export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0,
    };
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <CounterButton onClick={this.increment} name="why" />
      </div>
    );
  }

  increment = (number) => {
    this.setState({
      counter: this.state.counter + number,
    });
  };
}

1640328947(1).png

案例--页面切换显示不同内容:

1640331967(1).png

//父:App.jsx

import React, { Component } from "react";
import TabControl from "./TabControl";

export default class App extends Component {
  state = {
    currentTitle: "新款",
    titles: ["新款", "精选", "流行"],
  };

  render() {
    const { currentTitle, titles } = this.state;

    return (
      <div>
        <TabControl
          titles={titles}
          itemClickTitle={(index) => this.itemClickTitle(index)}
        />
        <h2 className="zgc">{currentTitle}</h2>
      </div>
    );
  }

  itemClickTitle(index) {
    this.setState({
      currentTitle: this.state.titles[index],
    });
  }
}

//子:TabControl.jsx

import React, { Component } from "react";
import PropTypes from "prop-types";

export default class TabControl extends Component {
  state = {
    currentIndex: 0,
  };

  render() {
    const { titles } = this.props;
    const { currentIndex } = this.state;

    return (
      <div className="tab-control">
        {titles.map((item, index) => {
          return (
            <div
              key={item}
              className={"tab-item " + (index === currentIndex ? "active" : "")}
              onClick={() => this.itemClick(index)}
            >
              <span>{item}</span>
            </div>
          );
        })}
      </div>
    );
  }

  itemClick(index) {
    this.setState({
      currentIndex: index,
    });

    this.props.itemClickTitle(index);
  }
}

TabControl.propTypes = {
  titles: PropTypes.array.isRequired,
};

//样式:style.css

.tab-control {
  display: flex;
  height: 44px;
  line-height: 44px;
}

.tab-item {
  flex: 1;
  text-align: center;
}

.tab-item span {
  padding: 5px 8px;
}

.tab-item.active {
  color: red;
}

.tab-item.active span {
  border-bottom: 3px solid red;
}

.zgc{
  text-align: center;
  font-size: 80px;
  font-family: '方正舒体';
}

3. refs

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

在我们正常的操作节点时,需要采用DOM API 来查找元素,但是这样违背了 React 的理念,因此有了refs

有三种操作refs的方法,分别为:

  • 字符串形式
  • 回调形式
  • createRef形式

字符串形式refs

将DOM节点存储在refs中,引用this.refs.input1

<!-- 准备好一个“容器” -->
<div id="test"></div>

<script type="text/babel">
//创建组件
class Demo extends React.Component {
    //展示左侧输入框的数据
    showData = () => {
        const input1 = this.refs.input1
        alert(input1.value)
        console.log(this);
    }
    //展示右侧输入框的数据
    showData2 = () => {
            const { input2 } = this.refs
            alert(input2.value)
    }
    render() {
            return (
                <div>
                    <input ref="input1" type="text" placeholder="点击按钮提示数据" />&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
                </div>
        )
    }
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2" />, document.getElementById('test'))
</script>

虽然这个方法不推荐,但是还能用,还很好用hhh~它已过时并可能会在未来的版本被移除。

回调形式的refs

组件实例的ref属性传递一个回调函数c => this.input1 = c (箭头函数简写),这样会在实例的属性中存储对DOM节点的引用,使用时可通过this.input1来使用

使用方法

<!-- 准备好一个“容器” -->
<div id="test"></div>

<script type="text/babel">
//创建组件
class Demo extends React.Component {
        //展示左侧输入框的数据
        showData = () => {
                const input1 = this.input1
                alert(input1.value)
                console.log(this);
        }
        //展示右侧输入框的数据
        showData2 = () => {
                //解构赋值
                const { input2 } = this
                alert(input2.value)
                console.log(this);
        }
        render() {
            return (
                <div>
                    {/*回调形式的ref,传入的参数为ref绑定的DOM元素,最后将DOM元素赋给组件自身的属性input1*/}
                    <input ref={(c) => { this.input1 = c }} type="text" placeholder="点击按钮提示数据" />&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据" />&nbsp;
                </div>
        )
    }
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2" />, document.getElementById('test'))
</script>

我的理解

c会接收到当前节点作为参数,ref的值为函数的返回值,也就是this.input1 = c,因此是给实例下的input1赋值

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的class绑定函数的写法

<!-- 准备好一个“容器” -->
<div id="test"></div>

<script type="text/babel">
//创建组件
class Demo extends React.Component{

        state = {isHot:false}

        showInfo = ()=>{
                const {input1} = this
                alert(input1.value)
        }

        changeWeather = ()=>{
                //获取原来的状态
                const {isHot} = this.state
                //更新状态
                this.setState({isHot:!isHot})
        }

        saveInput = (c)=>{
                this.input1 = c;
                console.log('@',c);
        }

        render(){
            const {isHot} = this.state
            return(
                <div>
                    <h2>今天天气很{isHot ? '炎热':'凉爽'}</h2>
                    {/*<input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/}
                    <input ref={this.saveInput} type="text"/><br/><br/>
                    <button onClick={this.showInfo}>点我提示输入的数据</button>
                    <button onClick={this.changeWeather}>点我切换天气</button>
                </div>
            )
        }
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>

createRef 形式(推荐写法)

React 给我们提供了一个相应的API,它会自动的将该 DOM 元素放入实例对象中

<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
        //创建组件
class Demo extends React.Component{
/* 
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
 */
        myRef = React.createRef()
        myRef2 = React.createRef()
        //展示左侧输入框的数据
        showData = ()=>{
                alert(this.myRef.current.value);
        }
        //展示右侧输入框的数据
        showData2 = ()=>{
                alert(this.myRef2.current.value);
        }
        render(){
            return(
                <div>
                    <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
                </div>
            )
        }
}
        //渲染组件到页面
        ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>

注意:我们不要过度的使用 ref,如果发生时间的元素刚好是需要操作的元素,就可以使用事件对象去替代。过度使用有什么问题我也不清楚,可能有 bug 吧

案例

通过ref在父组件中直接调用子组件的方法

import React, { PureComponent, createRef } from "react";

class Counter extends PureComponent {
  state = {
    counter: 0,
  };

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={(e) => this.increment()}>+1</button>
      </div>
    );
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1,
    });
  }
}

export default class App extends PureComponent {
  counterRef = createRef();

  render() {
    return (
      <div>
        <Counter ref={this.counterRef} />
        <button onClick={() => this.appBtnClick()}>App按钮</button>
      </div>
    );
  }

  appBtnClick() {
    this.counterRef.current.increment();
  }
}

4. 事件处理

(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) —为了的高效
(2).通过event.target得到发生事件的DOM元素对象 ——不要过度使用ref

<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
   //创建组件
class Demo extends React.Component{
    //创建ref容器
    myRef = React.createRef()
    myRef2 = React.createRef()

    //展示左侧输入框的数据
    showData = (event)=>{
        // console.log(event.target);
        // 此处不能用event,因为触发事件的元素为button
        alert(this.myRef.current.value);
    }

    //展示右侧输入框的数据
    showData2 = (event)=>{
        alert(event.target.value);
    }
//发生事件的元素正好是你要操作的元素,可以省略ref
    render(){
        return(
            <div>
                <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
                <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
            </div>
        )
    }
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>

5.非受控组件与受控组件

受控组件

在 HTML 中,表单元素(如<input>、 <textarea> 和 <select>)通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。

我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做受控组件

案例1:受控组件的基本使用

import React, { PureComponent } from "react";

export default class App extends PureComponent {
  state = {
    username: "",
  };

  render() {
    return (
      <div>
        <form onSubmit={(e) => this.handleSubmit(e)}>
          <label htmlFor="username">
            用户:
            {/* 受控组件 */}
            <input
              type="text"
              id="username"
              onChange={(e) => this.handleChange(e)}
              value={this.state.username}
            />
          </label>
          <input type="submit" value="提交" />
        </form>
      </div>
    );
  }

  handleSubmit(event) {
    event.preventDefault();
    console.log(this.state.username);
  }

  handleChange(event) {
    this.setState({
      username: event.target.value,
    });
  }
}

案例2:受控组件的select使用

import React, { PureComponent } from 'react'

export default class App extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      fruits: "orange"
    }
  }

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <select name="fruits" 
                  onChange={e => this.handleChange(e)}
                  value={this.state.fruits}>
            <option value="apple">苹果</option>
            <option value="banana">香蕉</option>
            <option value="orange">橘子</option>
          </select>
          <input type="submit" value="提交"/>
        </form>
      </div>
    )
  }

  handleSubmit(event) {
    event.preventDefault();
    console.log(this.state.fruits);
  }

  handleChange(event) {
    this.setState({
      fruits: event.target.value
    })
  }
}

案例3:受控组件多输入简写

import React, { PureComponent } from "react";

export default class App extends PureComponent {
  state = {
    username: "",
    password: "",
    valid: "",
  };

  render() {
    return (
      <div>
        <form onSubmit={(e) => this.handleSubmit(e)}>
          <label htmlFor="username">
            用户:
            <input
              type="text"
              id="username"
              name="username"
              onChange={(e) => this.handleChange(e)}
              value={this.state.username}
            />
          </label>
          <br />
          <label htmlFor="password">
            密码:
            <input
              type="text"
              id="password"
              name="password"
              onChange={(e) => this.handleChange(e)}
              value={this.state.password}
            />
          </label>
          <br />
          <label htmlFor="valid">
            验证:
            <input
              type="text"
              id="valid"
              name="valid"
              onChange={(e) => this.handleChange(e)}
              value={this.state.valid}
            />
          </label>
          <br />
          <input type="submit" value="提交" />
        </form>
      </div>
    );
  }

  handleSubmit(event) {
    event.preventDefault();
    const { username, password, valid } = this.state;
    console.log(username, password, valid);
  }

  handleChange(event) {
    // console.log(event.target.name);
    this.setState({
      // 计算属性名
      [event.target.name]: event.target.value,
    });
  }

   // 计算属性名 可以声明 一个变量 然后给对象当做属性使用

  // var name= 'nickName'
  // var obj = {
  //     [name]:'王麻子'
  // }

  // handleUsernameChange(event) {
  //   this.setState({
  //     username: event.target.value
  //   })
  // }

  // handlePasswordChange(event) {
  //   this.setState({
  //     password: event.target.value
  //   })
  // }

  // handleValidChange(event) {
  //   this.setState({
  //     valid: event.target.value
  //   })
  // }
}

非受控组件

在大多数情况下,我们推荐使用 受控组件 来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。

要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以 使用 ref 来从 DOM 节点中获取表单数据。

1640589420(1).png

import React, { PureComponent, createRef } from "react";

export default class App extends PureComponent {

  usernameRef = createRef();

  render() {
    return (
      <div>
        <form onSubmit={(e) => this.handleSubmit(e)}>
          <label htmlFor="username">
            用户:
            <input type="text" id="username" ref={this.usernameRef} />
          </label>
          <input type="submit" value="提交" />
        </form>
      </div>
    );
  }

  handleSubmit(event) {
    event.preventDefault();
    console.log(this.usernameRef.current.value);
  }
}

四、 购物车案例

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

    <style>
      table {
        border: 1px solid #eee;
        border-collapse: collapse;
      }

      th,
      td {
        border: 1px solid #eee;
        padding: 10px 16px;
        text-align: center;
      }

      th {
        background-color: #ccc;
      }

      .count {
        margin: 0 5px;
      }
    </style>
  </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 = {
            books: [
              {
                id: 1,
                name: "《算法导论》",
                date: "2006-9",
                price: 85.0,
                count: 2,
              },
              {
                id: 2,
                name: "《UNIX编程艺术》",
                date: "2006-2",
                price: 59.0,
                count: 1,
              },
              {
                id: 3,
                name: "《编程珠玑》",
                date: "2008-10",
                price: 39.0,
                count: 1,
              },
              {
                id: 4,
                name: "《代码大全》",
                date: "2006-3",
                price: 128.0,
                count: 1,
              },
            ],
          };
        }
        // 格式化数据
        formatPrice(price) {
          if (typeof price !== "number") {
            price = Number(price) || 0;
          }

          return "¥" + price.toFixed(2);
        }
        //
        changeBookCount(index, count) {
           // React中设计原则: state中的数据的不可变性;所以不能直接修改原数组
          const newBooks = [...this.state.books];
          newBooks[index].count += count;
          this.setState({
            books: newBooks,
          });
        }
        //移出书籍
        removeBook(index) {
          // React中设计原则: state中的数据的不可变性;
          this.setState({
            books: this.state.books.filter((item, indey) => index != indey),
          });
        }

        // 计算总价格
        getTotalPrice() {
          // 1.for循环的方式
          // let totalPrice = 0;
          // for (let item of this.state.books) {
          //   totalPrice += item.price * item.count;
          // }
          // return formatPrice(totalPrice);

          // 2.filter/map/reduce(归纳为)
          // 回调函数的参数:
          // 参数一: 上一次回调函数的结果(第一次没有上一次函数的回调函数的结果, 使用初始化值)
          const totalPrice = this.state.books.reduce((preValue, item) => {
            return preValue + item.count * item.price;
          }, 0);

          return this.formatPrice(totalPrice);
        }
        // 表格显示格式函数
        renderBooks() {
          return (
            <div>
              <table>
                <thead>
                  <tr>
                    <th></th>
                    <th>书籍名称</th>
                    <th>出版日期</th>
                    <th>价格</th>
                    <th>购买数量</th>
                    <th>操作</th>
                  </tr>
                </thead>
                <tbody>
                  {this.state.books.map((item, index) => {
                    return (
                      <tr key={index + 1}>
                        <td>{index + 1}</td>
                        <td>{item.name}</td>
                        <td>{item.date}</td>
                        <td>{this.formatPrice(item.price)}</td>
                        <td>
                          <button
                          {/*书籍数量小于等于1减号按钮不能在交互了*/}
                            disabled={item.count <= 1}
                            onClick={(e) => this.changeBookCount(index, -1)}
                          >
                            -
                          </button>
                          <span className="count">{item.count}</span>
                          <button
                            onClick={(e) => this.changeBookCount(index, 1)}
                          >
                            +
                          </button>
                        </td>
                        <td>
                          <button onClick={(e) => this.removeBook(index)}>
                            移除
                          </button>
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
              <h2>总价格: {this.getTotalPrice()}</h2>
            </div>
          );
        }
        // 表格为空函数
        renderEmptyTip() {
          return <h2>购物车为空~</h2>;
        }

        render() {
          return this.state.books.length
            ? this.renderBooks()
            : this.renderEmptyTip();
        }
      }

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

Snipaste_2022-07-05_19-53-53.png