01.React 基础

React 基础

这篇文章并非完全零基础,零基础最好先看一下官网文档入门,并搭配我另外一篇关于 React 用法的博客更佳:《React 使用》

0. 构建项目

Create React App

1. 代码优化

1.1 构造器里使用 bind 绑定 this

constructor(props) {
    super(props);
    this.state = {
        inputValue: "",
        list: [],
    };
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleBtnClick = this.handleBtnClick.bind(this);
    this.handleItemDelete = this.handleItemDelete.bind(this);
}

1.2 setState 里使用异步

handleInputChange(e) {
    // 使用异步,提升性能
    const value = e.target.value;
    this.setState(() => ({
        inputValue: value,
    }));
}

因为 setState 是异步的,所以有个回调函数用来对 setState 后的处理

handleInputChange(e) {
    // 使用异步,提升性能
    const value = e.target.value;
    this.setState(() => ({
        inputValue: value,
    }), () => {
        // setState 后获取 ul 里 div 长度,放外边会比 setState 先执行
        console.log(this.ul.querySelectorAll('div').length)
    });
}

1.3 使用 prevState

prevState 相当于 this.state

handleBtnClick() {
    // prevState 修改之前的数据,避免不小心修改 state 的状态
    this.setState((prevState) => ({
        list: [...prevState.list, prevState.inputValue],
        inputValue: "",
    }));
}

优化前

handleItemDelete(index) {
    // immutable
    // state 不允许我们做任何的改变
    // 拷贝数组
    const list = [...this.state.list];
    list.splice(index, 1);
    this.setState(({
        list,
    }));
}

优化后

handleItemDelete(index) {
    this.setState((prevState) => {
        const list = [...prevState.list];
        list.splice(index, 1);
        return {
            list,
        };
    });
}

2. React 衍生的思考

声明式开发,不同于命令式(频繁操作 DOM),专注于数据的构建,减少大量 DOM 操作代码。

可以与其他框架共存。不操作 id 为 root 的 div 就行。

组件化,首字母大写形式。

单向数据流,父组件可以给子组件传值,但是子组件不可以改变父组件的值。如果没有单向数据流,导致其他使用的子组件会受到影响。子组件只能调用父组件传过来的方法,最终还是由父组件进行数据的改变。

视图层框架,只负责数据渲染到页面的过程,传值等功能交给其他框架实现。

函数式编程。维护容易,各司其职,不改变原数据,函数式编程方便测试。

3. PropTypes 与 DefaultProps

PropTypes 类型的强校验。不阻止运行,但是会发出警告。

DefaultProps 默认属性,表示没传递的时候的默认值。

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

export default class TodoItem extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    const { onItemDelete } = this.props;
    onItemDelete(this.props.index);
  }

  render() {
    const { item, text } = this.props;
    return (
      <div onClick={this.handleClick}>
        {text} - {item}
      </div>
    );
  }
}

TodoItem.propTypes = {
    // isRequired 表示必须要传递,没传会报错
  text: PropTypes.string.isRequired,
  // 传过来的值必须分别是 string, 函数, number
  // oneOfType 表示支持多种类型
  item: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  onItemDelete: PropTypes.func,
  index: PropTypes.number,
};

// defaultProps 设置默认值
TodoItem.defaultProps = {
  text: "hello world",
};

4. Props,State 与 Rander 函数的关系

当组件的 state 或者 props 发生改变的时候,render 函数会重新执行。

当父组件的 render 函数被运行时,它的子组件的 render 都将被重新执行一次。

5. 虚拟 DOM

普通想法:
  1. state 数据
  2. JSX 模板
  3. 数据 + 模板结合,生成真实的 DOM 来显示
  4. state 发生改变
  5. 数据 + 模板结合,生成真实的 DOM,用来替换原始的 DOM

缺陷:

第一次生成了一个完整的 DOM 片段

第二次生成了一个完整的 DOM 片段

第二次生成的 DOM 替换第一个 DOM,非常耗性能

优化:
  1. state 数据
  2. JSX 模板
  3. 数据 + 模板结合,生成真实的 DOM 来显示
  4. state 发生改变
  5. 数据 + 模板结合,生成真实的 DOM,并不直接替换原始的 DOM
  6. 新的 DOM (DocumentFragment) 和原始的 DOM 作比对,找差异
  7. 找到了 input 发生了变化
  8. 只用新的 DOM 中的 input 元素,替换掉老的 DOM 中的 input 元素

缺陷:

性能的提升并不明显,因为比对需要消耗性能

React 做法:
  1. state 数据
  2. JSX 模板
  3. 数据 + 模板结合,生成虚拟 DOM (虚拟 DOM 就是一个 JS 对象,用它来描述真实的 DOM)
  4. 用虚拟 DOM 的解耦生成真实的 DOM,来显示
  5. state 发生变化
  6. 数据 + 模板 生成新的虚拟 DOM (极大的提升了性能)
  7. 比较原始虚拟 DOM 和新的虚拟 DOM 的区别,找到区别是 span 中内容 (极大的提升性能)
  8. 直接操作 DOM,改变 span 中的内容

虚拟 DOM 虽然有性能损耗,但是损耗极小,创建 JS 对象损耗小。相比之下,真实 DOM 的生成需要调用 Web Application API

6. 深入了解虚拟 DOM

JSX -> React.createElement -> JS 对象 -> 真实的 DOM

优点:

  1. 性能提升了。
  2. 它使得跨端应用得以实现。React Native

7. 虚拟 DOM 中的 Diff 算法

DIff 算法比较原始虚拟 DOM 和新的虚拟 DOM 的区别

image-20220313110507036

为什么 setState 设置为异步可以提升性能?

如果 setState 连续调用三次,时间很短的话会合并起来只进行一次 diff 比对。

比较方法:

同级比较,如果不同,同级以及以下的节点都被替换。

假设有一个节点数组,两个虚拟 DOM 的节点数组进行比对,使用 key 进行比对,如果一致的话,就可以进行复用。因此需要有稳定的 key 值来提升性能。

8. React 中 ref 的使用

未使用 ref
<input
    id="insertArea"
    type="text"
    className="input"
    value={this.state.inputValue}
    onChange={this.handleInputChange}
/>

e.target 就是一个 DOM 节点

handleInputChange(e) {
    const value = e.target.value;
    this.setState(() => ({
        inputValue: value,
    }));
}
使用 ref
<input
    id="insertArea"
    type="text"
    className="input"
    value={this.state.inputValue}
    onChange={this.handleInputChange}
    ref={(input) => {this.input = input}}
/>

构建一个 ref 引用,this.input 指向当前的 input DOM 元素

handleInputChange() {
    // 使用异步,提升性能
    const value = this.input.value;
    this.setState(() => ({
        inputValue: value,
    }));
}

但是 React 并不推荐这样做,鼓励数据驱动,尽量不要操作 DOM

9. 生命周期函数

生命周期函数指在某一时刻会自动调用执行的函数

React 通常将组件⽣命周期分为三个阶段:

  • 挂载阶段(Mount),组件第⼀次在DOM树中被渲染的过程;

  • 更新过程(Update),组件状态发⽣变化,重新更新渲染的过程;

  • 卸载过程(Unmount),组件从DOM树中被移除的过程;

9.1 旧版生命周期函数

Mounting

componentWillMount:组件被挂载到页面前执行。

componentDidMount:组件被挂载到页面之后,自动被执行。

Updation

componentWillReceiveProps:没有 props 函数,不会被触发。当组件从父组件接收 props 的时候,且父组件的 render 函数重新被执行了,该函数触发。该组件之前已经存在于父组件中,才会被执行。

shouldComponentUpdate: 组件被更新之前,会自动被执行,如果返回值为 false,组件不会更新。

componentWillUpdate:组件被更新之前执行,但是在 shouldComponentUpdate 之后执行,取决于返回结果是否为 true。

componentDidUpdate:组件更新完成之后,将会被执行。

Unmounting

componentWillUnmount:当这个组件即将被从页面中剔除的时候,会被执行。

9.2 新版生命周期函数

10. 生命周期函数的使用场景

10.1 防止重复渲染

shouldComponentUpdate:父组件 render 的时候,子组件也会跟着 render,但是往往子组件内容并没有发生改变。这样会造成性能的损耗。

image-20220313182439204

比如上面的 todolist,里边的 list 就没必要跟着父组件渲染。

性能优化

防止无谓的 render

// 可接收两个参数
// nextProps 指将要接收的新 props
// nextState 指将要接收的新 state
shouldComponentUpdate(nextProps, nextState) {
    // 如果新的 props 内容不变,则不 render
    if (nextProps.item !== this.props.item) {
        return true;
    }
    return false;
}
引申

在构造器里进行 this 作用域的绑定,只执行一次,有效提升性能。

constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
}

10.2 Ajax 请求

Ajax 不能放在 render 中进行获取数据,因为会造成死循环(每次 render 都会发送一次请求)。componentDidMount 在挂载的时候才会触发,且仅触发一次,因此放 componentDidMount 里边比较合适。

11. 使用 Charles 实现本地数据 mock

1. 创建 todolist.json 虚拟数据
["sjh", "React", "male"]
2. 添加映射

Tools -> Map Local

上面的配置有点问题,还是 404

解决办法:

Host 设置为 localhost.charlesproxy.com

此时,输入 http://localhost.charlesproxy.com:3000/ ,正常发送 Ajax 请求

但是这样写比较麻烦,所以可以写一个新映射 Tools -> Map Remote

image-20220314001139292

将域名映射到 http://localhost:3001/

但是这个写法在谷歌 90 后的版本会报错 strict-origin-when-cross-origin

使用 axios 发送请求:

componentDidMount() {
    axios
        .get("/api/todolist")
        .then((res) => {
        this.setState(() => ({
            // 拷贝,避免修改 res 造成不可预知的问题
            list: [...res.data],
        }));
    })
        .catch((err) => {
        throw new Error(err);
    });
}

请求结果:

12. React 中 CSS 实现动画效果

有两种写法,一种使用 transition,另一种使用 animation。只能实现简单动画

在 jsx 中:

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

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      show: true,
    };
    this.handleToggle = this.handleToggle.bind(this);
  }

  handleToggle() {
    this.setState((prevState) => ({
      show: prevState.show ? false : true,
    }));
  }

  render() {
    return (
      <>
        <div className={this.state.show ? "show" : "hide"}>hello</div>
        <button onClick={this.handleToggle}>toggle</button>
      </>
    );
  }
}

第一种方法,在 css 中:

.show {
  opacity: 1;
  transition: all 1s ease-in;
}

.hide {
  opacity: 0;
  transition: all 1s ease-in;
}

第二种方法,在 css 中:

.input {
  border: 1px solid red;
}

.show {
  animation: show-item 2s ease-in forwards;
}

.hide {
  animation: hide-item 2s ease-in forwards;
}

@keyframes show-item {
  0% {
    opacity: 0;
    color: red;
  }
  50% {
    opacity: 0.5;
    color: green;
  }
  100% {
    opacity: 1;
    color: blue;
  }
}

@keyframes hide-item {
  0% {
    opacity: 1;
    color: red;
  }
  50% {
    opacity: 0.5;
    color: green;
  }
  100% {
    opacity: 0;
    color: blue;
  }
}

13. 使用 react-transition-group 实现动画

13.1 安装 react-transition-group

# npm
npm install react-transition-group --save

# yarn
yarn add react-transition-group

13.2 写代码 - CSSTransition

通过按钮实现文本的显示和隐藏

import React, { Component } from "react";
import "./style.css";
import { CSSTransition } from "react-transition-group";

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      show: true,
    };
    this.handleToggle = this.handleToggle.bind(this);
  }

  handleToggle() {
    this.setState((prevState) => ({
      show: prevState.show ? false : true,
    }));
  }

  render() {
    return (
      <>
        <CSSTransition in={this.state.show} timeout={1000} classNames="fade">
          {/* 包裹后,自动执行内部类名的变更操作 */}
          {/* <div className={this.state.show ? "show" : "hide"}>hello</div> */}
          <div>hello</div>
        </CSSTransition>
        <button onClick={this.handleToggle}>toggle</button>
      </>
    );
  }
}
/* 入场动画的瞬间状态 (false -> true) */
.fade-enter {
  opacity: 0;
}

/* 入场动画进入后到完成前 */
.fade-enter-active {
  opacity: 1;
  transition: opacity 1s ease-in;
}

/* 动画结束后的状态 */
.fade-enter-done {
  opacity: 1;
}

/* 离场动画的瞬间状态 (true -> false) */
.fade-exit {
  opacity: 1;
}

/* 离场动画进入后到完成前 */
.fade-exit-active {
  opacity: 0;
  transition: opacity 1s ease-in;
}

.fade-exit-done {
  opacity: 0;
}

如果控制台报错:

Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Transition which is inside StrictMode. Instead, add a ref directly to the element you want to reference.

则需要关闭React的“严格模式”。把 index.js 里的 React.StrictMode 标签删了就好啦。

虽然写法变麻烦了,但是带来了许多新特性。React Transition Group

13.3 写第二组代码 - TransitionGroup

要求:按钮点击的时候增加 item,并附加动画效果。

TransitionGroup 的效果在于,能给包裹的一组数据都赋予动画效果。每个具体的标签之外,还需要添加一层 CSSTransition。此时的 in 属性就没有必要使用了。

import React, { Component } from "react";
import "./style.css";
import { CSSTransition, TransitionGroup } from "react-transition-group";

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      list: [],
    };
    this.handleAddItem = this.handleAddItem.bind(this);
  }

  handleAddItem() {
    this.setState((prevState) => ({
      list: [...prevState.list, "item"],
    }));
  }

  render() {
    return (
      <>
        <TransitionGroup>
          {this.state.list.map((item, index) => {
            return (
              <CSSTransition timeout={1000} classNames="show" key={index}>
                <div>{item}</div>
              </CSSTransition>
            );
          })}
        </TransitionGroup>
        <button onClick={this.handleAddItem}>Add</button>
      </>
    );
  }
}
/* 入场动画的瞬间状态 (false -> true) */
.show-enter {
  opacity: 0;
}

/* 入场动画进入后到完成前 */
.show-enter-active {
  opacity: 1;
  transition: opacity 1s ease-in;
}

/* 动画结束后的状态 */
.show-enter-done {
  opacity: 1;
}

/* 如果要做消失动画,添加相关样式即可 */
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值