Skip to content

React 全家桶(技术栈)

01. React 入门

1.1 React 简介

1.1.1 官网

1.1.2 介绍描述

  1. 用于动态构建用户界面的 JavaScript 库(只关注于视图)
  2. 由 Facebook 开源

1.1.3 React 的特点

  1. 声明式编码
  2. 组件化编码
  3. React Native 编写原生应用
  4. 高效(优秀的 Diffing 算法)

1.1.4 React 高效的原因

  1. 使用虚拟(virtual)DOM, 不总是直接操作页面真实 DOM。
  2. DOM Diffing 算法, 最小化页面重绘。

1.2 React 的基本使用

1.2.1 效果

html
<div>
  <h1>Hello,React</h1>
</div>

1.2.2 相关 js 库

  1. react.js:React 核心库。
  2. react-dom.js:提供操作 DOM 的 react 扩展库。
  3. babel.min.js:解析 JSX 语法代码转为 JS 代码的库。

1.2.3 创建虚拟 DOM 的两种方式

  1. 纯 JS 方式(一般不用)
  2. JSX 方式

1.2.4 虚拟 DOM 与真实 DOM

  1. React 提供了一些 API 来创建一种 “特别” 的一般 js 对象
    • const VDOM = React.createElement('xx',{id:'xx'},'xx')
    • 上面创建的就是一个简单的虚拟 DOM 对象
  2. 虚拟 DOM 对象最终都会被 React 转换为真实的 DOM
  3. 我们编码时基本只需要操作 react 的虚拟 DOM 相关数据, react 会转换为真实 DOM 变化而更新界面。

1.3 React JSX

1.3.1 效果

html
<div>
  <h1>Hello,jsx</h1>
</div>

1.3.2 JSX

  1. 全称: JavaScript XML
  2. react 定义的一种类似于 XML 的 JS 扩展语法: JS + XML 本质是 React.createElement(component, props, ...children)方法的语法糖
  3. 作用: 用来简化创建虚拟 DOM
    • 写法:var ele = <h1>Hello JSX!</h1>
    • 注意 1:它不是字符串, 也不是 HTML/XML 标签
    • 注意 2:它最终产生的就是一个 JS 对象
  4. 标签名任意: HTML 标签或其它标签
  5. 标签属性任意: HTML 标签属性或其它
  6. 基本语法规则
    • 遇到 <开头的代码, 以标签的语法解析: html 同名标签转换为 html 同名元素, 其它标签需要特别解析
    • 遇到以 { 开头的代码,以 JS 语法解析: 标签中的 js 表达式必须用{ }包含
  7. babel.js 的作用
    • 浏览器不能直接解析 JSX 代码, 需要 babel 转译为纯 JS 的代码才能运行
    • 只要用了 JSX,都要加上 type="text/babel", 声明需要 babel 来处理

1.3.3 渲染虚拟 DOM(元素)

  1. 语法: ReactDOM.render(virtualDOM, containerDOM)
  2. 作用: 将虚拟 DOM 元素渲染到页面中的真实容器 DOM 中显示
  3. 参数说明
    • 参数一: 纯 js 或 jsx 创建的虚拟 dom 对象
    • 参数二: 用来包含虚拟 DOM 元素的真实 dom 元素对象(一般是一个 div)

1.3.4 JSX 练习

  • 需求: 动态展示列表
html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>hello_react</title>
    <!-- 用于将jsx转化为js -->
    <script src="../js/babel.min.js"></script>
    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 用于支持dom操作 -->
    <script src="../js/react-dom.development.js"></script>
  </head>
  <body>
    <!-- 准备容器 -->
    <div id="list"></div>

    <script type="text/babel">
      /* 此处一定要写babel */
      // 1.创建虚拟dom
      const VDOM = (
        <ul>
          <li>Vue</li>
          <li>React</li>
          <li>Angular</li>
        </ul>
      );
      // 2.渲染虚拟dom到页面
      ReactDOM.render(VDOM, document.getElementById("list"));
    </script>
  </body>
</html>

1.4 模块与组件、模块化与组件化的理解

1.4.1 模块

  1. 理解:向外提供特定功能的 js 程序, 一般就是一个 js 文件
  2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
  3. 作用:复用 js, 简化 js 的编写, 提高 js 运行效率

1.4.2 组件

  1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image 等等)
  2. 为什么要用组件: 一个界面的功能更复杂
  3. 作用:复用编码, 简化项目编码, 提高运行效率

1.4.3 模块化

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

1.4.4 组件化

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

02. 基本理解和使用

2.1 基本理解和使用

2.1.1 使用 React 开发者工具调试

  • 在浏览器中导入react_dev_tools插件

2.1.2 效果

函数式组件:

js
// 1.创建函数式组件
function MyComponent() {
  console.log(this); // 此处的this是undefined,因为babel编译后开启了严格模式use strict
  return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>;
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById("test"));
/*
  执行了渲染函数之后,发生了:
  1.React会解析组件标签,找到MyComponent组件。
  2.发现组件是由函数定义的,随后调用函数,将返回的虚拟DOM转为真实DOM并渲染。
*/

类式组件:

js
// 1.创建类式组件
class MyComponent extends React.Component {
  render() {
    // render是放在MyComponent的原型上,供实例使用。
    // render中的this,是MyComponents的实例对象
    console.log(this);
    console.log(this instanceof MyComponent);
    return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>;
  }
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById("test"));
/*
  执行了渲染函数之后,发生了:
  1.React会解析组件标签,找到MyComponent组件。
  2.发现组件是由类定义的,随后new出来MyComponent类的实例,并通过该实例调用到原型上的render方法。
  3.将render返回的虚拟DOM转化为真实DOM并渲染。
*/

2.1.3 注意

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

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

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

2.2 属性 1: state

2.2.1 效果

需求: 定义一个展示天气信息的组件

  1. 默认展示天气炎热 或 凉爽
  2. 点击文字切换天气

2.2.2 理解

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

2.2.3 强烈注意

  1. 组件中 render 方法中的 this 为组件实例对象
  2. 组件自定义的方法中 this 为 undefined,如何解决?
    • 强制绑定 this: 通过函数对象的 bind()
    • 箭头函数
  3. 状态数据,不能直接修改或更新,需要使用 setState 函数
  4. state 的简写方式:
js
// 1.创建类式组件
class Weather extends React.Component {
  // 初始化状态
  state = {
    isHot: true,
  };
  render() {
    const { isHot } = this.state;
    return (
      <div>
        <h2 onClick={this.changeWeather}>
          今天天气很{isHot ? "炎热" : "凉爽"}
        </h2>
      </div>
    );
  }
  // 自定义方法,要用赋值语句+箭头函数的写法
  changeWeather = () => {
    const { isHot } = this.state;
    this.setState({
      isHot: !isHot,
    });
  };
}

// 2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));

2.3 属性 2: props

2.3.1 效果

需求: 自定义用来显示一个人员信息的组件

  1. 姓名必须指定,且为字符串类型;
  2. 性别为字符串类型,如果性别没有指定,默认为男
  3. 年龄为字符串类型,且为数字类型,默认值为 18
js
class Person extends React.Component {
  render() {
    const { name, age, gender } = this.props;
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{gender}</li>
        <li>年龄:{age + 1}</li>
      </ul>
    );
  }
}
ReactDOM.render(
  <Person name="jerry" age={18} gender="男" />,
  document.getElementById("test1")
);
ReactDOM.render(
  <Person name="tom" age={16} gender="女" />,
  document.getElementById("test2")
);
const p = { name: "cat", age: 18, gender: "男" };
// console.log('@', ...p)
ReactDOM.render(<Person {...p} />, document.getElementById("test3"));

2.3.2 理解

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

2.3.3 作用

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

2.3.4 编码操作

  1. 内部读取某个属性值
js
this.props.xxx;
  1. 对 props 中的属性值进行类型限制和必要性限制 第一种方式(React v15.5 开始已弃用):
js
Person.propTypes = {
  name: React.PropTypes.string.isRequired, // 限制name为必传、字符串
  gender: React.PropTypes.string, // 限制gender为字符串
  age: React.PropTypes.number, // 限制age为数字
};

第二种方式(新):使用 prop-types 库进限制(需要引入 prop-types 库)

js
Person.propTypes = {
  name: PropTypes.string.isRequired, // 限制name为必传、字符串
  gender: PropTypes.string, // 限制gender为字符串
  age: PropTypes.number, // 限制age为数字
};
  1. 扩展属性: 将对象的所有属性通过 props 传递
js
<Person {...args} />
  1. 默认属性值:
js
Person.defaultProps = {
  gender: "未知", // sex默认值
  age: 18, // age默认值
};
  1. 组件类的构造函数
js
constructor(props) {
  // 构造器是否接收props,是否传递给super取决于是否希望在构造器中,通过this访问props
  super(props)
  // 如果上方不传props,则this.props为undefined
  console.log(this.props)
}
  1. props 的简写方式:
js
class Person extends React.Component {
  // 对标签属性进行类型、必要性限制
  static propTypes = {
    name: PropTypes.string.isRequired, // 限制name为必传、字符串
    gender: PropTypes.string, // 限制gender为字符串
    age: PropTypes.number, // 限制age为数字
    speak: PropTypes.func, // 限制speak为函数
  };
  // 置顶标签属性默认值
  static defaultProps = {
    gender: "未知", // sex默认值
    age: 18, // age默认值
    city: "天津",
  };
  constructor(props) {
    // 构造器是否接收props,是否传递给super取决于是否希望在构造器中,通过this访问props
    super(props);
    console.log(this.props);
  }
  render() {
    console.log(this);
    const { name, age, gender } = this.props;
    // this.props.name = '123' // props是只读的
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{gender}</li>
        <li>年龄:{age + 1}</li>
      </ul>
    );
  }
}

ReactDOM.render(
  <Person name="Jack" age={19} />,
  document.getElementById("test1")
);

2.4 属性 3: refs 与事件处理

2.4.1 效果

需求: 自定义组件, 功能说明如下:

  1. 点击按钮, 提示第一个输入框中的值
  2. 当第 2 个输入框失去焦点时, 提示这个输入框中的值
  • 代码如下:
js
class Demo extends React.Component {
  render() {
    return (
      <div>
        <input ref="input1" type="text" placeholder="点击按钮提示" />
        <button onClick={this.handleClick}>点击提示左侧数据</button>
        <hr />
        <input
          ref="input2"
          type="text"
          placeholder="失去焦点提示"
          onBlur={this.handleBlur}
        />
      </div>
    );
  }
  // 点击按钮提示
  handleClick = () => {
    const { input1 } = this.refs;
    alert(input1.value);
  };
  // input失去焦点
  handleBlur = () => {
    const { input2 } = this.refs;
    alert(input2.value);
  };
}
ReactDOM.render(<Demo />, document.getElementById("test1"));

2.4.2 理解

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

2.4.3 编码

  1. 字符串形式的 ref【尽量不要使用,会有效率问题】
html
<input ref="input1" />
  1. 回调形式的 ref【以内联函数定义时,组件更新会被执行两次】
jsx
<input ref={(c) => (this.input1 = c)} />
  1. createRef 创建 ref 容器
jsx
myRef = React.createRef()
<input ref={this.myRef} />

2.4.4 事件处理

  1. 通过 onXxx 属性指定事件处理函数(注意大小写)
    • React 使用的是自定义(合成)事件, 而不是使用的原生 DOM 事件
    • React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
  2. 通过 event.target 得到发生事件的 DOM 元素对象

2.5 收集表单数据

2.5.1 效果

需求:

  • 定义一个包含表单的组件
  • 输入用户名密码后, 点击登录提示输入信息

2.5.2 理解

包含表单的组件分类:

  1. 受控组件
js
class Login extends React.Component {
  state = {
    username: "",
    password: "",
  };
  render() {
    return (
      <form onSubmit={this.handleLogin}>
        账号:
        <input onChange={this.usernameChange} type="text" name="username" />
        <br />
        密码:
        <input onChange={this.passwordChange} type="password" name="password" />
        <br />
        <button>登录</button>
      </form>
    );
  }
  usernameChange = (e) => {
    this.setState({
      username: e.target.value,
    });
  };
  passwordChange = (e) => {
    this.setState({
      password: e.target.value,
    });
  };
  handleLogin = (e) => {
    e.preventDefault();
    const { username, password } = this.state;
    alert(`账号:${username}, 密码:${password}`);
  };
}
ReactDOM.render(<Login />, document.getElementById("test1"));
  1. 非受控组件
js
class Login extends React.Component {
  myInput1 = React.createRef();
  myInput2 = React.createRef();
  render() {
    return (
      <form onSubmit={this.handleLogin}>
        账号:
        <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>
    );
  }
  handleLogin = (e) => {
    e.preventDefault();
    const { username, password } = this;
    alert(`账号:${username.value}, 密码:${password.value}`);
  };
}
ReactDOM.render(<Login />, document.getElementById("test1"));

2.6 组件的生命周期

2.6.1 效果

需求:定义组件实现以下功能:

  1. 让指定的文本做显示 / 隐藏的渐变动画
  2. 从完全可见,到彻底消失,耗时 2S
  3. 点击按钮从界面中卸载组件
js
//创建组件
class Life extends React.Component {
  state = {
    opacity: 1,
  };
  // 组件挂载完毕
  componentDidMount() {
    this.timer = setInterval(() => {
      let { opacity } = this.state;
      this.setState({
        opacity: opacity <= 0 ? 1 : opacity - 0.1,
      });
    }, 200);
  }
  // 组件将要卸载
  componentWillUnmount() {
    clearInterval(this.timer);
  }
  // 初始化渲染、状态更新之后 1 + n
  render() {
    console.log("render");
    // 挂载
    return (
      <div>
        <h2 style={{ opacity: this.state.opacity }}>React学不会怎么办</h2>
        <button onClick={this.BtnClick}>点击</button>
      </div>
    );
  }
  BtnClick = () => {
    // 卸载
    ReactDOM.unmountComponentAtNode(document.getElementById("test"));
  };
}

// 渲染组件
ReactDOM.render(<Life />, document.getElementById("test"));

2.6.2 理解

  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React 组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

2.6.3 生命周期流程图(旧)

生命周期流程图旧

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

  • 初始化阶段: 由 ReactDOM.render()触发---初次渲染
  1. constructor()
  2. componentWillMount()
  3. render()
  4. componentDidMount()
  • 更新阶段: 由组件内部 this.setSate()或父组件重新 render 触发
  1. shouldComponentUpdate()
  2. componentWillUpdate()
  3. render()
  4. componentDidUpdate()
  • 卸载组件: 由 ReactDOM.unmountComponentAtNode()触发
  1. componentWillUnmount()
js
//创建组件
class Count extends React.Component {
  // 构造函数
  constructor(props) {
    console.log("Count---constructor");
    super(props);
    this.state = {
      count: 0,
    };
  }
  // 组件即将挂载
  componentWillMount() {
    console.log("Count---componentWillMount");
  }
  // 组件挂载完成
  componentDidMount() {
    console.log("Count---componentDidMount");
  }

  // 组件是否应该更新
  shouldComponentUpdate() {
    console.log("Count---shouldComponentUpdate");
    return true;
  }
  // 强制组件更新
  forceUpdateByReact = () => {
    this.forceUpdate();
  };
  // 组件即将更新
  componentWillUpdate() {
    console.log("Count---componentWillUpdate");
  }
  // 组件完成更新
  componentDidUpdate() {
    console.log("Count---componentDidUpdate");
  }

  // 组件即将卸载
  componentWillUnmount() {
    console.log("Count---componentWillUnmount");
  }
  // 渲染组件
  render() {
    console.log("Count---render");
    const { count } = this.state;
    return (
      <div>
        <h2>当前数字:{count}</h2>
        <button onClick={this.add}>点击</button>
        <br />
        <button onClick={this.forceUpdateByReact}>强制更新</button>
        <br />
        <button onClick={this.kill}>卸载组件</button>
      </div>
    );
  }
  add = () => {
    let { count } = this.state;
    this.setState({
      count: count + 1,
    });
  };
  kill = () => {
    ReactDOM.unmountComponentAtNode(document.getElementById("test"));
  };
}

class A extends React.Component {
  state = {
    carName: "奔驰",
  };
  render() {
    return (
      <div>
        <div>我是A组件</div>
        <button onClick={this.changeCar}>换车</button>
        <B carName={this.state.carName} />
      </div>
    );
  }
  changeCar = () => {
    this.setState({
      carName: "奥拓",
    });
  };
}

class B extends React.Component {
  // 组件即将接收Props
  componentWillReceiveProps(props) {
    console.log("B---componentWillReceiveProps", props);
  }
  // 组件是否应该更新
  shouldComponentUpdate() {
    console.log("B---shouldComponentUpdate");
    return true;
  }
  // 组件即将更新
  componentWillUpdate() {
    console.log("B---componentWillUpdate");
  }
  // 组件完成更新
  componentDidUpdate() {
    console.log("B---componentDidUpdate");
  }
  render() {
    console.log("B---render");
    return <div>我是B组件,车名:{this.props.carName}</div>;
  }
}

// 渲染组件
ReactDOM.render(<Count />, document.getElementById("test"));
// ReactDOM.render(<A />, document.getElementById('test'))

2.6.4 生命周期流程图(新)

生命周期流程图新

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

  • 初始化阶段: 由 ReactDOM.render()触发---初次渲染
  1. constructor()
  2. getDerivedStateFromProps
  3. render()
  4. componentDidMount()
  • 更新阶段: 由组件内部 this.setSate()或父组件重新 render 触发
  1. getDerivedStateFromProps
  2. shouldComponentUpdate()
  3. render()
  4. getSnapshotBeforeUpdate
  5. componentDidUpdate()
  • 卸载组件: 由 ReactDOM.unmountComponentAtNode()触发
  1. componentWillUnmount()
js
//创建组件
class Count extends React.Component {
  // 构造函数
  constructor(props) {
    console.log("Count---constructor");
    super(props);
    this.state = {
      count: 0,
    };
  }

  // 通过传入的值获取派生的状态
  static getDerivedStateFromProps(props, state) {
    console.log("Count---getDerivedStateFromProps", props, state);
    return null;
  }

  // 在更新之前获取快照
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("getSnapshotBeforeUpdate", prevProps, prevState);
    return "atCat";
  }

  // 组件挂载完成
  componentDidMount() {
    console.log("Count---componentDidMount");
  }

  // 组件是否应该更新
  shouldComponentUpdate() {
    console.log("Count---shouldComponentUpdate");
    return true;
  }
  // 强制组件更新
  forceUpdateByReact = () => {
    this.forceUpdate();
  };
  // 组件完成更新
  componentDidUpdate(prevProps, prevState, snapshotValue) {
    console.log(
      "Count---componentDidUpdate",
      prevProps,
      prevState,
      snapshotValue
    );
  }

  // 组件即将卸载
  componentWillUnmount() {
    console.log("Count---componentWillUnmount");
  }
  // 渲染组件
  render() {
    console.log("Count---render");
    const { count } = this.state;
    return (
      <div>
        <h2>当前数字:{count}</h2>
        <button onClick={this.add}>点击</button>
        <br />
        <button onClick={this.forceUpdateByReact}>强制更新</button>
        <br />
        <button onClick={this.kill}>卸载组件</button>
      </div>
    );
  }
  add = () => {
    let { count } = this.state;
    this.setState({
      count: count + 1,
    });
  };
  kill = () => {
    ReactDOM.unmountComponentAtNode(document.getElementById("test"));
  };
}

// 渲染组件
ReactDOM.render(<Count count={199} />, document.getElementById("test"));

2.6.5 重要的勾子

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

2.6.6 即将废弃的勾子

  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate 现在使用会出现警告,下一个大版本需要加上 UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

03. React 应用(React 脚手架)

3.1 创建 react 应用

3.1.1 react 脚手架

  1. xxx 脚手架: 用来帮助程序员快速创建一个基于 xxx 库的模板项目
    1. 包含了所有需要的配置(语法检查、jsx 编译、devServer…)
    2. 下载好了所有相关的依赖
    3. 可以直接运行一个简单效果
  2. react 提供了一个用于创建 react 项目的脚手架库: create-react-app
  3. 项目的整体技术架构为: react + webpack + es6 + eslint
  4. 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化

3.1.2 创建项目并启动

  • 第一步,全局安装:npm i -g create-react-app
  • 第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
  • 第三步,进入项目文件夹:cd hello-react
  • 第四步,启动项目:npm start

3.1.3 react 脚手架项目结构

ls
public ---- 静态资源文件夹
  favicon.icon ------ 网站页签图标
  index.html -------- 主页面
  logo192.png ------- logo图
  logo512.png ------- logo图
  manifest.json ----- 应用加壳的配置文件
  robots.txt -------- 爬虫协议文件
src ---- 源码文件夹
  App.css -------- App组件的样式
  App.js --------- App组件
  App.test.js ---- 用于给App做测试
  index.css ------ 样式
  index.js ------- 入口文件
  logo.svg ------- logo图
  reportWebVitals.js --- 页面性能分析文件(需要web-vitals库的支持)
  setupTests.js ---- 组件单元测试的文件(需要jest-dom库的支持)

3.1.4 功能界面的组件化编码流程(通用)

  1. 拆分组件: 拆分界面,抽取组件
  2. 实现静态组件: 使用组件实现静态页面效果
  3. 实现动态组件:
    • 3.1 动态显示初始化数据
      • 3.1.1 数据类型
      • 3.1.2 数据名称
      • 3.1.2 保存在哪个组件?
    • 3.2 交互(从绑定事件监听开始)

3.2 组件的组合使用-TodoList

功能: 组件化实现此功能

  1. 显示所有 todo 列表
  2. 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本
  • 父传子 props【字符串、布尔值、对象】
  • 子传父 props【函数】
  • uuid【安装:yarn add nanoid】【使用:nanoid()
  • TodoList 案例

04. React ajax

4.1 理解

4.1.1 前置说明

  1. React 本身只关注于界面, 并不包含发送 ajax 请求的代码
  2. 前端应用需要通过 ajax 请求与后台进行交互(json 数据)
  3. react 应用中需要集成第三方 ajax 库(或自己封装)

4.1.2 常用的 ajax 请求库

  1. jQuery: 比较重, 如果需要另外引入不建议使用
  2. axios: 轻量级, 建议使用
    • 封装 XmlHttpRequest 对象的 ajax
    • promise 风格
    • 可以用在浏览器端和 node 服务器端

4.2 react 脚手架配置代理

方法一

在 package.json 中追加如下配置

json
"proxy": "http://127.0.0.1:5000"

说明:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了 3000 不存在的资源时,那么该请求会转发给 5000 (优先匹配前端资源)

方法二

  1. 第一步:创建代理配置文件,在 src 下创建配置文件:src/setupProxy.js
  2. 编写 setupProxy.js 配置具体代理规则:
js
const proxy = require("http-proxy-middleware");
module.exports = function (app) {
  app.use(
    proxy.createProxyMiddleware("/api1", {
      //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
      target: "http://127.0.0.1:5000", //配置转发目标地址(能返回数据的服务器地址)
      changeOrigin: true, //控制服务器接收到的请求头中host字段的值
      /*
      changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
      changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
      changeOrigin默认值为false,但我们一般将changeOrigin值设为true
      */
      pathRewrite: { "^/api1": "" }, //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
    }),
    proxy.createProxyMiddleware("/api2", {
      target: "http://127.0.0.1:5001",
      changeOrigin: true,
      pathRewrite: { "^/api2": "" },
    })
  );
};
  • 说明:
  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。
  • 注意:
  1. 低版本的 http-proxy-middleware 在配置代理时,不需要加createProxyMiddleware,高版本必须加createProxyMiddleware

4.3 axios

4.3.1 文档

axios 官方文档

4.3.2 相关 API

  1. GET 请求
js
axios
  .get("/user?ID=12345")
  .then(function (response) {
    console.log(response.data);
  })
  .catch(function (error) {
    console.log(error);
  });

axios
  .get("/user", {
    params: {
      ID: 12345,
    },
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
  1. POST 请求
js
axios
  .post("/user", {
    firstName: "Fred",
    lastName: "Flintstone",
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

4.4 案例—github 用户搜索

4.5 消息订阅-发布机制

  1. 工具库: PubSubJS
  2. 下载: npm install pubsub-js --save
  3. 使用:
    • import PubSub from 'pubsub-js' //引入
    • const token = PubSub.subscribe('delete', function(_, data){ }); //订阅
    • PubSub.publish('delete', data) //发布消息
    • PubSub.unsubscribe(token) //移除

4.6 扩展:Fetch

4.6.1 文档

  1. 官方文档
  2. 思否文档
  3. 案例地址

4.6.2 特点

  1. fetch: 原生函数,不再使用 XmlHttpRequest 对象提交 ajax 请求
  2. 老版本浏览器可能不支持

4.6.3 相关 API

  1. GET 请求
js
fetch(url)
  .then(function (response) {
    return response.json();
  })
  .then(function (data) {
    console.log(data);
  })
  .catch(function (e) {
    console.log(e);
  });
  1. POST 请求
js
fetch(url, {
  method: "POST",
  body: JSON.stringify(data),
})
  .then(function (data) {
    console.log(data);
  })
  .catch(function (e) {
    console.log(e);
  });

05. React 路由

5.1 相关理解

5.1.1 SPA 的理解

  1. 单页 Web 应用(single page web application,SPA)。
  2. 整个应用只有一个完整的页面。
  3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。
  4. 数据都需要通过 ajax 请求获取, 并在前端异步展现。

5.1.2 路由的理解

  1. 什么是路由?
    1. 一个路由就是一个映射关系(key:value)
    2. key 为路径, value 可能是 function 或 component
  2. 路由分类
    1. 后端路由:
      • 理解: value 是 function, 用来处理客户端提交的请求。
      • 注册路由: router.get(path, function(req, res))
      • 工作过程:当 node 接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
    2. 前端路由:
      • 浏览器端路由,value 是 component,用于展示页面内容。
      • 注册路由: <Route path="/test" component={Test}>
      • 工作过程:当浏览器的 path 变为/test 时, 当前路由组件就会变为 Test 组件

5.1.3 react-router-dom 的理解

  1. react 的一个插件库。
  2. 专门用来实现一个 SPA 应用。
  3. 基于 react 的项目基本都会用到此库。

5.2 react-router-dom 相关 API

5.2.1 内置组件

  1. <BrowserRouter> history 模式的包裹
  2. <HashRouter> hash 模式的包裹
  3. <Route> 注册路由组件
  4. <Redirect> 重定向组件
  5. <Link> 跳转组件
  6. <NavLink> 跳转组件-高亮匹配
  7. <Switch> 路由匹配到即停止

5.2.2 其它

  1. history 对象
  2. match 对象
  3. withRouter 函数

5.3 路由的使用

5.3.1 路由的基本使用

  1. 明确好界面中的组件构造
  2. 导航区的 a 标签改为<Link to=""></Link>标签
  3. 内容区的展示组件改为<Route path="" component={} />标签
  4. <App />标签外包裹<BrowserRouter><HashRouter>

5.3.2 路由组件与一般组件的差异

  1. 写法不同:
    • 一般组件<Demo />
    • 路由组件<Route path="/demo" component={Demo} />
  2. 存放位置不同:
    • 一般组件:component
    • 路由组件:pages
  3. 接收到的 props 不同
    • 一般组件:父组件传递进来的数据
    • 路由组件:接收路由传递的属性
    js
        history:
          go: ƒ go(n)
          goBack: ƒ goBack()
          goForward: ƒ goForward()
          push: ƒ push(path, state)
          replace: ƒ replace(path, state)
        location:
          pathname: "/about"
          search: ""
          state: undefined
        match:
          isExact: true
          params: {}
          path: "/about"
          url: "/about"

使用场景:当需要给路由添加高亮时

html
就可以把
<Link to="/about">About</Link>
替换为:
<NavLink to="/about">About</NavLink>
  1. 当切换到对应的路由时,会给对应的 a 标签添加active
  2. 可通过修改activeClassName,来修改高亮className
  3. 示例:
html
<NavLink activeClassName="ecat" className="list-group-item" to="/about"
  >About</NavLink
>
简写:
<NavLink
  activeClassName="ecat"
  className="list-group-item"
  to="/about"
  children="About"
/>
  1. 案例地址

    可通过this.props.children来获取组件标签体内容

5.3.4 Switch 使用

  1. 通常情况下,Route的 path 和 component 是一一对应的关系。
  2. Switch可以提高路由的匹配效率【匹配到即停止】。
html
<Switch>
  <Route path="/home" component="{Home}" />
  <Route path="/home" component="{Test}" />
  <Route path="/about" component="{About}" />
</Switch>

5.3.5 样式缺失修复

  1. public 目录下的index.html中引用文件使用绝对路径
html
<link rel="stylesheet" href="/css/bootstrap.css" />
  1. public 目录下的index.html中引用文件使用%PUBLIC_URL%
html
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css" />
  1. <App />组件使用<HashRouter>包裹
jsx
<HashRouter>
  <App />
</HashRouter>

5.3.6 路由的严格匹配和模糊匹配

  1. 默认使用的是模糊匹配,【输入的路径】必须要包含【匹配的路径】,且顺序一致
  2. 开启严格模式<Route exact path="" component={} />
  3. 严格模式不要随意开启,需要再打开,否则会导致无法匹配二级路由

5.3.7 Redirect 使用

  1. 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到 Redirect 指定的路由
  2. 示例:
jsx
<Switch>
  <Route path="/home" component={Home} />
  <Route path="/about" component={About} />
  <Redirect to="/home" />
</Switch>

5.3.8 嵌套路由

  1. 注册子路由时要写上父路由的 path 值
  2. 路由的匹配是按照路由注册的顺序进行的

5.4 路由传参

5.4.1 params 传参

  1. 路由链接(携带参数)
jsx
<Link to=`/home/news/detail/${id}`>新闻1</Link>
  1. 注册路由(接收参数)
jsx
<Route path="/home/news/detail/:id" component={Detail} />
  1. 组件接参
jsx
const { id } = this.props.match.params;

5.4.2 search 参数

  1. 路由链接(携带参数)
jsx
<Link to=`/home/news/detail/?id=${id}`>新闻1</Link>
  1. 注册路由(无需接收参数)
jsx
<Route path="/home/news/detail" component={Detail} />
  1. 组件接参
jsx
import qs from "querystringify";
// 获取到的search是一个urlencoded编码字符串,需要解析
const { id } = qs.parse(this.props.location.search);

5.4.3 state 参数

  1. 路由链接(携带参数)
jsx
<Link to={{ pathname: "/home/news/detail", state: { id: item.id } }}>
  新闻1
</Link>
  1. 注册路由(无需接收参数)
jsx
<Route path="/home/news/detail" component={Detail} />
  1. 组件接参
jsx
const { id } = this.props.location.state;

5.5 路由跳转模式

  1. push
jsx
<Link to={{ pathname: "/home/news/detail", state: { id: item.id } }}>
  新闻1
</Link>
  1. replace
jsx
<Link replace to={{ pathname: "/home/news/detail", state: { id: item.id } }}>
  新闻1
</Link>

5.6 编程式路由

借助this.props.history对象上的 API 对路由进行跳转、前进、后退操作

jsx
this.props.history.push(); // 跳转
this.props.history.replace(); // 替换
this.props.history.goBack(); // 后退
this.props.history.goForward(); // 前进
this.props.history.go(); // 前进||后退

5.7 withRouter

一般组件的props中没有路由组件所持有的 history API

  • 需要使用withRouter
  • withRouter 可以加工一般组件,让一般组件可以具备路由组件所特有的 API
  • withRouter 返回的是一个新组件
  • 示例:
jsx
import React, { Component } from "react";
import { withRouter } from "react-router-dom";

class Header extends Component {
  forward = () => {
    this.props.history.goForward();
  };
  back = () => {
    this.props.history.goBack();
  };
  go = () => {
    this.props.history.go(-1);
  };
  render() {
    console.log("一般组件收到的props", this.props);
    return (
      <div className="page-header">
        <h2>React Router Demo</h2>
        <button onClick={this.forward}>前进</button>
        <button onClick={this.back}>后退</button>
        <button onClick={this.go}>go</button>
      </div>
    );
  }
}

export default withRouter(Header);

5.8 BrowserRouter 与 HashRouter 的区别

  1. 底层原理不一样:
    1. BrowserRouter 使用的是 H5 的 history API,不兼容 IE9 及以下版本。
    2. HashRouter 使用的是 URL 的哈希值。
  2. path 表现形式不一样
    1. BrowserRouter 的路径中没有#,例如:localhost:3000/demo/test
    2. HashRouter 的路径包含#,例如:localhost:3000/#/demo/test
  3. 刷新后对路由 state 参数的影响
    1. BrowserRouter 没有任何影响,因为 state 保存在 history 对象中。
    2. HashRouter 刷新后会导致路由 state 参数的丢失!!!
  4. 备注:HashRouter 可以用于解决一些路径错误相关的问题。

06. 开源 React UI 组件库

6.1 material-ui(国外)

  1. 官网地址
  2. github 地址

6.2 ant-design(国内蚂蚁金服)

  1. 官网地址
  2. github 地址
  3. 安装依赖
sh
yarn add antd
# or
npm install antd --save
  1. 使用
jsx
import React from "react";
import { DatePicker } from "antd";

const App = () => {
  return <DatePicker />;
};

export default App;
  1. 配置默认主题
jsx
import React from "react";
import ReactDOM from "react-dom/client";
import { ConfigProvider } from "antd";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
  <ConfigProvider
    theme={{
      token: {
        colorPrimary: "#00b96b",
      },
      components: {
        Button: {
          colorPrimary: "#0ff",
        },
      },
    }}
  >
    <App />
  </ConfigProvider>
);