二、React面向组件编程

郁子大约 10 分钟约 2946 字笔记React16.8尚硅谷张天禹

(一)基本理解和使用

1.使用 React 开发者工具调试

  • 基于 Chrome 浏览器:React Developer Tools
  • 提供方:Facebook / Meta 公司

2.函数式组件

  • 执行了 ReactDOM.render(...)之后,发生了什么?
    • React 解析组件标签,找到了 MyComponent 组件
    • 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中
  • 此处的 this 是 undefined,babel 编译后开启了严格模式,禁止自定义函数内的 this 指向 window
<script type="text/babel">
  {
    /* 1.创建函数式组件 */
  }
  function MyComponent() {
    console.log(this);
    return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>;
  }

  {
    /* 2.渲染组件到页面 */
  }
  ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>

3.类式组件

  • 执行了 ReactDOM.render(...)之后,发生了什么?
    • React 解析组件标签,找到了 MyComponent 组件
    • 发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的 render 方法
    • 将 render 返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中
  • render 是放在类的原型对象上,供实例使用
  • render 中的 this 是 MyComponent 的实例对象 / MyComponent 组件实例对象
<script type="text/babel">
  {
    /* 1.创建类式组件 */
  }
  class MyComponent extends React.Component {
    render() {
      console.log("render中的this:", this);
      return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>;
    }
  }

  {
    /* 2.渲染组件到页面 */
  }
  ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>

4.注意

  • 组件名必须首字母大写
  • 虚拟 DOM 元素只能有一个根元素
  • 虚拟 DOM 元素必须有结束标签

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

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

(二)组件三大核心属性 1:state

1.理解

  • state 是组件对象最重要的属性,值是对象
    • 可以包含多个 key-value 的组合
  • 组件被称为“状态机”,通过更新组件的 state 来更新对应的页面显示
    • 重新渲染组件

2.强烈注意

  • 组件中 render 方法中的 this 是组件实例对象
  • 组件自定义的方法中 thisundefined ,如何解决?
    • 强制绑定 this :通过函数对象的 bind()
    • 使用类中赋值语句+箭头函数的形式
  • 状态数据,不能直接修改或更新,需要使用 this.setState() API
class MyComponent extends React.component {
  // 强制绑定this,不推荐,无端引入构造器
  /*
    constructor(props) {
      super(props);
      this.changeWeather = this.changeWeather.bind(this);
    }
  */

  // 初始化状态
  state = {
    isHot: true,
  };

  // 自定义方法
  changeWeather = () => {
    // 修改状态数据
    this.setState({
      isHot: !isHot,
    });
  };

  render() {
    return <h2>今天天气很 {isHot ? "炎热" : "凉爽"} </h2>;
  }
}

(三)组件三大核心属性 2:props

1.理解

  • 每个组件对象都会有 props(properties 的简写)属性
  • 组件标签的所有属性都保存在 props

2.作用

  • 通过标签属性从 组件外组件内 传递变化的数据
  • 注意:组件内部不要修改 props 数据

3.编码操作

  • 内部读取某个属性值:this.props.name
  • props 中的属性值进行类型限制和必要性限制

1)方式一

  • React v15.5 开始已弃用
Person.propTypes = {
  name: React.PropTypes.string.isRequired,
  age: React.Prototypes.number,
};

2)方式二

  • 使用 prop-types 库进行限制,需要引入 prop-types
Person.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
};
  • 扩展属性:将对象的所有属性通过 props 传递
<Person {...person} />
  • 默认属性值
Person.defaultProps = {
  age: 18,
  sex: "男",
};
  • 组件类的构造函数
constructor(props) {
	super(props);
	console.log(props); // 打印所有属性
}

(四)组件三大核心属性 3:refs 与事件处理

1.理解

  • 组件内的标签可以定义 ref 属性来标识自己

2.字符串形式

<input ref="input1" />

3.回调形式

<input ref={(c) => (this.input1 = c)}

4.createRef 创建 ref 容器

myRef = React.creatRef();

render() {
	<input ref={this.myRef} />
}

5.事件处理

  • 通过 onXxxx 属性指定事件处理函数(注意大小写)
    • onclick => onClick, onblur => onBlur, ......
    • React 使用的是自定义(合成)事件,而不是使用的原生 DOM 事件 —— 为了更好的兼容性
    • React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素) —— 为了更好的高效性
  • 通过 event.target 得到发生事件的 DOM 元素对象
    • 勿过度使用 ref
    • 当发生事件的元素正好是要操作的元素时,事件回调可以收到 event 参数
// 展示右侧输入框的数据
showData2 = (event) => {
  // alert(this.myRef2.current.value);
  alert(event.target.value);
}

render() {
  return <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据" />
}

(五)收集表单数据

1.受控组件【推荐】

  • 将表单中的输入类 DOM 数据通过监听 onChange 事件保存到状态中,需要用时再从状态中读取
class Login extends React.Component {
  // 初始化状态
  state = {
    username: "",
    password: "",
  };

  // 保存用户名到状态中
  saveUsername = (event) => {
    console.log(event.target.value);
    this.setState({ username: event.target.value });
  };

  // 保存密码到状态中
  savePassword = (event) => {
    console.log(event.target.value);
    this.setState({ password: event.target.value });
  };

  // 表单提交的回调
  handleSubmit = (event) => {
    event.preventDefault(); // 阻止表单提交
    console.log(this.state);
    const { username, password } = this.state;
    alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
  };

  render() {
    return (
      // <form action="http://www.atguigu.com" onSubmit={this.handleSubmit}>
      <form onSubmit={this.handleSubmit}>
        账号:
        <input onChange={this.saveUsername} type="text" name="username" />
        <br />
        密码:
        <input onChange={this.savePassword} type="password" name="password" />
        <br />
        <button>登录</button>
      </form>
    );
  }
}

2.非受控组件

  • 表单中所有输入类 DOM 的值都是现用现取的
class Login extends React.Component {
  handleSubmit = (event) => {
    event.preventDefault(); // 阻止表单提交
    const { username, password } = this;
    alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`);
  };

  render() {
    return (
      <form action="http://www.atguigu.com" onSubmit={this.handleSubmit}>
        账号:
        <input ref={(c) => (this.username = c)} type="text" name="username" />
        <br />
        密码:
        <input ref={(c) => (this.password = c)} type="password" name="password" />
        <br />
        <button>登录</button>
      </form>
    );
  }
}

(六)高阶函数-函数柯里化

1.高阶函数

  • 如果一个函数符合下面 2 个规范中的 任何一个 ,那该函数就是高阶函数
    • 若 A 函数,接收的参数是一个函数,那么 A 就可以称为高阶函数
    • 若 A 函数,调用的返回值依然是一个函数,那么 A 就可以称为高阶函数
  • 常见的高阶函数:
    • Promise
    • setTimeout、setInterval
    • arr.map()
    • .....

2.函数柯里化

  • 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
class Login extends React.Component {
  // 初始化状态
  state = {
    username: "",
    password: "",
  };

  // 保存表单数据到状态中
  /*
    // 该函数返回值是undefined
    saveFormData = (event) => {
      console.log(event.target.value);
      this.setState({ username: event.target.value });
    }
  */
  // 该函数返回值是一个高阶函数,返回的函数才是onChange事件的回调
  saveFormData = (dataType) => {
    return (event) => {
      console.log(dataType, event.target.value);
      // 以下两行代码等价,相当于给状态增加了'dataType'字段,应该使用[dataType]获取属性值
      // this.setState({ dataType: event.target.value });
      // this.setState({ 'dataType': event.target.value });
      this.setState({ [dataType]: event.target.value });
    };
  };

  render() {
    return (
      <form>
        {/*
          以下写法是将this.saveFormData('xxx')的返回值作为onChange事件的回调,不是将函数本身作为事件回调,
          所以事件只触发一次且是页面初始化之后立即触发
          <input onChange={this.saveFormData('username')} type="text" name="username" />

          以下写法等价,都是保证onChange事件的回调是一个函数
          <input onChange={this.saveFormData('username')} type="text" name="username" />
          <input onChange={this.saveFormData('username')()()} type="text" name="username" />
        */}
        账号:
        <input onChange={this.saveFormData("username")} type="text" name="username" />
        <br />
        密码:
        <input onChange={this.saveFormData("password")} type="password" name="password" />
        <br />
        <button>登录</button>
      </form>
    );
  }
}






















 


























(七)组件的生命周期

1.理解

  • 生命周期回调函数 / 生命周期钩子函数 / 生命周期函数 / 生命周期钩子
  • 组件从创建到死亡会经历一些特定的阶段
  • React 组件中包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用
  • 在定义组件时,会在特定的生命周期回调函数中做特定的工作

2.生命周期(旧)

  • 生命周期的三个阶段(旧):

1)初始化阶段(初次渲染):由 ReactDOM.render() 触发

  • constructor()
  • componentWillMount()
  • render()
  • componentDidMount() 【常用】
    • 一般在这个钩子中做一些初始化的事
    • 例如:开启定时器、发送网络请求、订阅消息

2)更新阶段:由组件内部 this.setState() 触发或父组件重新 render 渲染

  • shouldComponentUpdate()
  • componentWillUpdate()
  • render() 【必须】
  • componentDidUpdate()

3)卸载组件:由 ReactDOM.unmountComponentAtNode() 触发

  • componentWillUnmount() 【常用】
    • 一般在这个钩子中做一些收尾的事
    • 例如:关闭定时器、取消订阅消息

3.生命周期(新)

  • 生命周期的三个阶段(新):

1)初始化阶段(初次渲染):由 ReactDOM.render() 触发

  • constructor()
  • getDerivedStateFromProps()
    • state 的值在任何时候都取决于 props 时使用
    • 必须声明为 static
    • 返回 null 时不作任何处理
    • 返回 state object 时会覆盖 state 中已定义的同名数据,且后续不可更新
  • render()
  • componentDidMount() 【常用】
    • 一般在这个钩子中做一些初始化的事
    • 例如:开启定时器、发送网络请求、订阅消息

2)更新阶段:由组件内部 this.setState() 触发或父组件重新 render 渲染

  • getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render() 【必须】
  • getSnapshotBeforeUpdate()
    • 在更新之前获取快照
    • 在最近一次渲染输出(提交到 DOM 节点)之前调用
      • 当需要在组件发生更改之前从 DOM 中捕获一些信息时用
      • 如滚动位置
    • 返回值将作为参数传递给 componentDidUpdate()
  • componentDidUpdate()

3)卸载组件:由 ReactDOM.unmountComponentAtNode() 触发

  • componentWillUnmount() 【常用】
    • 一般在这个钩子中做一些收尾的事
    • 例如:关闭定时器、取消订阅消息

4.重要的钩子

  • render() :初始化渲染或更新渲染调用
  • componentDidMount() :开启监听,发送 ajax 请求
  • componentWillUnmount() :做一些收尾工作,如:清理定时器

5.即将废弃的钩子

  • componentWillMount()
  • componentWillReceiveProps()
  • componentWillUpdate()

相关信息

16 版本使用会出现警告,17 版本需要加上 UNSAFE_ 前缀才能使用,18 版本废弃

(八)虚拟 DOM 与 DOM Diffing 算法

1.经典面试题

  • react/vue 中的 key 有什么作用?( key 的内部原理是什么?)
  • 为什么遍历列表时,key 最好不要用 index

2.虚拟 DOM 中 key 的作用

1)简单地说

  • key 是虚拟 DOM 对象的标识,在更新显示时 key 起着极其重要的作用

2)详细地说

  • 当状态中的数据发生改变时,React 会根据【新数据】生成【新的虚拟 DOM】,随后 React 进行【新虚拟 DOM】与【旧虚拟 DOM】的 diff 比较,比较规则如下:
    • 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key
      • 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
      • 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
    • 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key
      • 根据数据创建新的真实 DOM,随后渲染到页面

3.用 index 作为 key 可能会引发的问题

  • 若对数据进行【逆序添加】、【逆序删除】等破坏顺序的操作
    • 会产生没有必要的真实 DOM 更新
    • 界面效果没问题,但是效率低
  • 若结构中还包含输入类的 DOM
    • 会产生错误的 DOM 更新
    • 界面效果有问题

注意

如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用 index 作为 key 是没有问题的

4.开发中如何选择 key

  • 最好使用每条数据的唯一标识作为 key
    • 比如 id、手机号、身份证号、学号等唯一值
  • 如果确定只是简单的展示数据,用 index 也是可以的
上次编辑于: