2

react核心概念及其应用

第1章:React入门

1.1 React基础认识

1.1.1 官方资料

React是什么?React是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库(只关注于View层)。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。

1.1.2 React的特点
  • Declarative 声明式: 以声明式编写UI,让代码更加可靠且方便调试
  • Component-Based 组件化: 创建拥有各自状态的组件,再由组件组成更加复杂的UI
  • Learn Once, Write Anywhere 一次学习,随处编写:无论什么技术栈,无需重写现有代码,引入React即可开发新功能,同时还可以进行服务器端渲染
  • 高效: (1)虚拟DOM,不直接操作DOM (2)DOM Diff算法,最小化页面重绘
  • 单向数据流: 父子组件采用props传递数据

1.2 React基本使用

如何将 React 组件添加到现有的 HTML 页面中。你可以基于自己现有的网站,或创建一个空的 HTML 文件来练习。

1.2.1 步骤 1:添加一个 DOM 容器到 HTML

首先,打开你想要编辑的 HTML 页面。添加一个空的 <div> 标签作为标记你想要用 React 显示内容的位置。例如:

<!-- ... 其它 HTML ... -->

<div id="like_button_container"></div>

<!-- ... 其它 HTML ... -->

我们给这个 <div> 加上唯一的 id HTML 属性。这将允许我们稍后用 JavaScript 代码找到它,并在其中显示一个 React 组件。React 会替换 DOM 容器内的任何已有内容。

1.2.2 步骤 2:添加 Script 标签

接下来,在 </body> 结束标签之前,向 HTML 页面中添加3个 <script> 标签, 前两个标签加载 React。第三个将加载组件代码:

<!-- ... 其它 HTML ... -->

<!-- 加载 React。-->
<!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>

<!-- 加载我们的 React 组件。-->
<script src="like_button.js"></script>
1.2.3 步骤 3:创建一个 React 组件

在 HTML 页面文件的同级目录下创建一个名为 like_button.js 的文件。在 like_button.js 的底部,加入以下两行代码。

const e = React.createElement

const domContainer = document.querySelector('#like_button_container');
ReactDOM.render(e('button',
  { onClick: () => this.setState({ liked: true }) },
  'Like'), domContainer);

这两行代码会找到我们在步骤 1 中添加到 HTML 里的 <div>,然后在它内部显示我们的 React 组件 “Like” 按钮。

1.2.4 步骤 4:使用 React 和 JSX

在上面的示例中,我们只依赖了浏览器原生支持的特性。这就是为什么我们使用了 JavaScript 函数调用来告诉 React 要显示什么, 但是,React 还提供了一种用 JSX 来代替实现的选择:

// 显示一个 "Like" <button>
return (
  <button onClick={() => this.setState({ liked: true })}>
    Like
  </button>
);

在项目中尝试 JSX 需要使用到babel, JSX代码所在的<script> 标签需要添加type="text/babel" 属性:

<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

但是这种引入方式不能使用在生产环境中。本节只是一个简单的原生示例,实际中项目我们都是推荐使用集成的工具链来实现最佳的用户和开发人员体验。

1.3 React JSX 以及虚拟DOM

1.3.1 虚拟DOM概念
  • React提供了一些API来创建“特别”的js对象,如上文应用的React.createElement方法
  • 虚拟DOM对象最终都会被React转换为真实的DOM并渲染。我们在写代码时关注点都在虚拟DOM的相关数据上

将一个元素渲染为DOM时,需要一段html声明根节点,然后传入只需把它们一起传入 ReactDOM.render()

// HTML片段,DOM根节点
<div id="root"></div>

// JS片段
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));

render方法的两个参数:纯js或jsx创建的虚拟DOM对象,以及用来包含虚拟DON元素的根节点对象

1.3.2 JSX相关概念

JSX——JavaScript XML,是React定义的一种类似于XML的拓展语法,其作用是用来创建React虚拟DOM对象JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。React 并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。

const element = <h1>Hello, world!</h1>;

语法规则:

  • 遇到< 开头的代码,以标签语法解析,html同名标签转换为html同名元素,其他标签需要特别解析,往往是组件
  • 遇到以{开头的代码,以JS语法解析。
  • 由于 JSX 会编译为 React.createElement 调用形式,所以 React 库也必须包含在 JSX 代码作用域内。即使没有显式地调用React及其组件,依然需要引入它
  • 用户自定义的组件必须的大写字母开头
  • 一个JSX语法片段只能返回一个根元素,所以必须有外层元素包裹。如果需要返回多个元素,Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。短语法是<></>

    <React.Fragment>
      <td>Hello</td>
      <td>World</td>
  • 布尔类型、Null 以及 Undefined 将会忽略, 以下元素渲染结果相同

    <div />
    <div></div>
    <div>{false}</div>
    <div>{null}</div>
    <div>{undefined}</div>
    <div>{true}</div>

    这有助于在特定条件下渲染元素

    <div>
      {showHeader && <Header />}
      <Content />
    </div>

    JSX本身也是表达式,在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。可以:

  • 在 if 语句和 for 循环的代码块中使用 JSX
  • 将 JSX 赋值给变量
  • 把 JSX 当作参数传入
  • 从函数中返回 JSX

    JSX中可以通过使用引号,来将属性值指定为字符串字面量:

    const element = <div tabIndex="0"></div>;

    因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

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

    1.4.1 模块
  • 理解:向外提供特定JS功能的程序,一般是一个JS文件
  • 为什么需要模块:现代化前端工程,尤其是大型工程,JS代码更多更复杂,需要更好的机制去优化开发环节的便利以及生产环节的加载性能
  • 作用:提高JS的复用能力,提高编程效率,提高运行效率

    1.4.2 组件
  • 理解: 组件是用来实现特定功能效果的代码集合,包括结构、样式与行为(HTML/CSS/JS)
  • 为什么需要组件:现代前端工程中一个界面的功能更加复杂,需要进行拆分并关注当前最小单元
  • 作用:提高JS的复用能力,提高编程效率,提高运行效率
  • React的组件分为:(1)继承React的class类组件(2)函数组件

    第2章:React面向组件编程

    2.1 基本理解和使用

    2.1.1 自定义组件
    // 方式一:工厂函数组件
    const MyFactoryComp = (props) => {
    return <h1>函数组件</h1>
    }
    // 方式二:ES6类组件
    class MyClassComp extends React.Component {
    render () {
      return <h1>类组件</h1>
    }
    }
    // 渲染组件
    ReactDom.render(<MyFactoryComp />, document.getElementById('root'))
    2.1.2 注意事项
  • 组件名首字母必须大写,没有大写的在引用之前都需要先赋予首字母大写的别名,然后再引用别名
  • 虚拟DOM元素只能有一个根元素,如果不方便插入根元素可使用<></>
  • 虚拟DOM元素必须有结束标签

    2.1.3 渲染过程
  • React创建内部虚拟DOM对象
  • 将其解析为真实的DOM片段
  • 插入到指定元素中,并替换元素下所有内容,所以#root元素节点的内容写了也是无效的

    2.2 组件三大属性之state

    2.2.1 定义及定义方法

    函数组件没有state,类组件才可以维护state,但是函数组件也有自己的hook来进行内部数据管理。类组件中有两种方式声明state并进行初始赋值:

  • class构造函数constructor中进行初始赋值

    class Clock extends React.Component {
      constructor(props) {
        super(props);
        this.state = {date: new Date()};
      }
    
      render() {
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
          </div>
        );
      }
    }
  • 直接定义state对象变量

    class Clock extends React.Component {
      state = {date: new Date()}
    
      render() {
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
          </div>
        );
      }
    }
    2.2.2 示例
    class Clock extends React.Component {
      constructor(props) {
        super(props);
        this.state = {date: new Date()};
      }
    
      componentDidMount() {
        this.timerID = setInterval(
          () => this.tick(),
          1000
        );
      }
    
      componentWillUnmount() {
        clearInterval(this.timerID);
      }
    
      tick() {
        this.setState({
          date: new Date()
        });
      }
    
      render() {
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Clock />,
      document.getElementById('root')
    );

    如何理解上述代码的调用顺序

    1. 当 <Clock /> 被传给 ReactDOM.render() 的时候,React 会调用 Clock 组件的构造函数。此时第一次更新state
    2. 之后 React 会调用组件的 render() 方法。组件第一次被渲染
    3. 当 Clock 的输出被插入到 DOM 中后,React 就会调用 ComponentDidMount() 生命周期方法。设置 tick() 方法
    4. 每秒都会调用一次 tick() 方法。更新setState(),重新调用 render() 方法
    5. 组件被移除时,调用 componentWillUnmount() 生命周期方法,计时器被移除

      2.2.3 注意事项
  • 不要直接修改state,需要使用setState(),这个函数可以接收一个对象,也可以接收一个函数,构造函数和声明处是唯一可以直接给state赋值的地方
  • state的更新可能是异步的,不能依赖他们的值来更新下一个状态,或者在代码后立即使用新值做一些事情

    // Correct
    this.setState((state, props) => ({
      counter: state.counter + props.increment
    }));
  • state的更新会被合并,当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state

    2.3 组件三大属性之props

    2.3.1 定义及定义方法

    组件的概念类似于JS函数,该函数可以理解为:入参——即组件标签上的所有属性(attributes)以及子组件(children)转换为单个对象传递给组件,即props,返回值:返回React元素
    在函数组件中,props是写明的唯一参数,在类组件中,props可以在组件内部直接引用

    2.3.2 示例
    // 函数组件举例
    function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
    }
    // 类组件举例
    class Welcome extends React.Component {
    render() {
      return <h1>Hello, {this.props.name}</h1>;
    }
    }
    // 组件调用
    const element = <Welcome name="Sara" />;
    ReactDOM.render(
    element,
    document.getElementById('root')
    );

    上例中,name="Sara"属性被赋予了组件,就会被转换为{name: 'Sara'}作为props传递给组件

    2.3.3 注意事项
  • props是只读的,无论是函数组件还是类组件,都不能改变自身的props,所有组件必须像维护纯函数一样保证props不被修改。

    2.4 组件三大属性之refs

    2.4.1 定义及定义方法

    在某些情况下,你需要在典型数据流之外强制修改子组件。如获取已渲染元素在屏幕上宽高、手动给input聚焦等,React提供了refs来获取真实的DOM。
    refs需要像state一样进行初始赋值

    2.4.2 示例

    Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。

    class MyComponent extends React.Component {
    constructor(props) {
      super(props);
      this.myRef = React.createRef(); // 将 Refs 分配给实例属性, 创建一个 ref 来存储 myRef 的 DOM 元素
    }
    
    componentDidMounted () {
      // 对该节点的引用可以在 ref 的 current 属性中被访问
      const node = this.myRef.current;
      ...
    }
    
    render() {
      return <div ref={this.myRef} />;
    }
    }
    2.4.3 注意事项
  • 如果一件事情可以通过声明式来完成,那最好不要使用 refs
  • refs 只能在类组件中使用,不能在函数组件中使用,因为它没有实例
  • ref 会在 componentDidMountcomponentDidUpdate 生命周期钩子触发前更新。挂载前,给current属性传入DOM元素,卸载前,传入null元素

    2.5 事件处理

    2.5.1 示例
    class Toggle extends React.Component {
    constructor(props) {
      super(props);
      this.state = {isToggleOn: true};
    
      // 为了在回调中使用 `this`,这个绑定是必不可少的
      this.handleClick = this.handleClick.bind(this);
    }
    
    handleClick() {
      this.setState(state => ({
        isToggleOn: !state.isToggleOn
      }));
    }
    
    render() {
      return (
        <button onClick={this.handleClick}>
          {this.state.isToggleOn ? 'ON' : 'OFF'}
        </button>
      );
    }
    }
    ReactDOM.render(
    <Toggle />,
    document.getElementById('root')
    );
    2.5.2 注意事项
  • 通过 onXxx (注意驼峰式)属性指定组件的事件处理函数(函数而非字符串)

    // 原生事件
    <button onclick="activateLasers()">
      Activate Lasers
    </button>
    // React事件
    <button onClick={activateLasers}>
      Activate Lasers
    </button>
  • 不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault

    function ActionLink() {
      function handleClick(e) {
        e.preventDefault();
        console.log('The link was clicked.');
      }
    
      return (
        <a href="#" onClick={handleClick}>
          Click me
        </a>
      );
    }
  • 参数 e 是一个合成事件,并且作为默认第一个参数传递,可以在回调函数内直接调用
  • 需要谨慎对待事件回调函数中的 thisclass 的方法默认不会绑定 this,如果函数内部需要调用类组件的 this, 需要显式绑定或者采用箭头函数声明回调函数

    2.6 组件生命周期

    2.6.1 生命周期定义

    生命周期是组件对象从创建到销毁所经历的特定的生命周期阶段。React组件对象定义了一系列的钩子函数,可以在生命周期的特定时刻执行这些函数。我们在定义组件中可以重写生命周期钩子函数,达到我们自己的目的。

    2.6.2 生命周期流程

    生命周期图表
    进行细分可以分为:

    阶段调用方法
    挂载阶段constructor() / static getDerivedStateFromProps() / render() / componentDidMount()
    更新阶段static getDerivedStateFromProps() / shouldComponentUpdate() / render() / getSnapshotBeforeUpdate() / componentDidUpdate()
    卸载componentWillUnmount()
    错误处理static getDerivedStateFromError() / componentDidCatch()
    2.6.3 注意事项
  • render() 方法是 class 组件中唯一必须实现的方法
  • constructor() 函数中不要调用 setState() 方法, 要避免在构造函数中引入任何副作用或订阅
  • 避免将 props 的值复制给 state
  • componentWillUnmount() 中需要卸载具有副作用的一些方法,比如定时器、取消网络请求、清除挂载时的订阅等

    2.7 HOOK

    ES6 class 组件可以在内部定义 state 进行数据管理,函数组件现在也有了类似功能的 Hook ,可以在不编写 class 的情况下使用 state 以及其他的React特性。以前我们说,复杂组件用类组件,函数组件只适合简单组件,有了Hooks支撑之后,函数组件也可以向类组件一样实现更加复杂的功能。
    Hook 是一些可以让你在函数组件里“钩入” React state生命周期等特性的函数。其中最常用的就是 State HookEffect Hook,前者提供了 useState 方法向组件内部添加一些 state ,后者则提供了useEffect方法来模拟 componentDidMountcomponentDidUpdatecomponentWillUnmount 生命周期。

    2.7.1 State Hook

    示例

    // 类组件写法
    class Example extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        count: 0
      };
    }
    
    render () {
      return (
        <>
          <button onClick={() => this.setState({ count: this.state.count + 1 })}>
            Click me
          </button>
          <p>You clicked {this.state.count} times</p>
        </>
      )
    }
    }
    // 函数组件写法
    import React, { useState } from 'react';
    function Example() {
    // 声明一个叫 “count” 的 state 变量
    const [count, setCount] = useState(0);
    const [fruit, setFruit] = useState('banana');
    
    return 
    <>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      <p>You clicked {count} times</p>
    </>
    )
  • 引入React中的 useState
  • 通过调用 useState 声明一个 state 变量,该函数返回一对值,通过方括号进行数组解构赋值,我们定义两个命名变量,第一个解构变量是 state 变量名,第二个解构变量是对该 state 变量进行更新的函数
  • 同类组件中的 state 变量一样,我们不能直接修改它,需要使用上述第二个解构变量进行间接修改它(如同类组件中的 setState 一样)
  • 函数组件内没有 this ,我们在引用变量的时候,直接使用上述第一个命名解构变量即可,无需使用 this.Xxx
  • 多个 state 变量可以进行多次进行声明和调用,与类组件中不同的是,对不同的 state 变量进行更新时,不再是合并,而是直接替换目标变量

    2.7.2 Effect Hook

    示例

    import React, { useState, useEffect } from 'react';
    function Example() {
    const [count, setCount] = useState(0);
    // Similar to componentDidMount and componentDidUpdate:
    useEffect(() => {
      // Update the document title using the browser API
      document.title = `You clicked ${count} times`;
    });
    
    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={() => setCount(count + 1)}>
          Click me
        </button>
      </div>
    );
    }
  • useEffect Hook 有两个参数,第一个参数是一个带有副作用的函数,在DOM更新之后会调用它,即 componentDidMountcomponentDidUpdate 生命周期钩子之后。第二个参数是内部 useState 和函数组件参数——即props 中若干变量组成的数组,如果省略第二个参数,则意味着每次更新渲染之后都会执行这个 useEffect Hook。如果我们的 useEffect Hook 只依赖于部分变量的修改,可以在数组中订阅它。如果只想在挂载和卸载时执行,传入空数组 [] 就好了。
  • 有些 useEffect Hook 是需要被手动清除的。当订阅外部数据源,清除工作非常重要,可以防止引起内存泄露。WHY???当然是闭包。useEffect Hook 返回一个函数,React 将会在执行清除操作时调用它,相当于 componentWillUnmount 生命周期调用。

    import React, { useState, useEffect } from 'react';
    
    function FriendStatus(props) {
      const [isOnline, setIsOnline] = useState(null);
    
      useEffect(() => {
        function handleStatusChange(status) {
          setIsOnline(status.isOnline);
        }
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        // Specify how to clean up after this effect:
        return function cleanup() {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      });
    
      if (isOnline === null) {
        return 'Loading...';
      }
      return isOnline ? 'Online' : 'Offline';
    }
    2.7.3 其他常用Hook
  • useCallback
    返回一个 memoized 回调函数。

    const memoizedCallback = useCallback(
      () => {
        doSomething(a, b);
      },
      [a, b],
    );

    把内联回调函数及依赖项数组作为参数传入 useCallback ,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate )的子组件时,它将非常有用。

  • useMemo
    返回一个 memoized 值。

    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

    把“创建”函数和依赖项数组作为参数传入 useMemo ,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

  • useReducer
  • useRef
  • useImperativeHandle
  • useLayoutEffect
  • useDebugValue
  • 自定义Hook

    2.7.4 注意事项
  • 只在最顶层使用Hook, 不要在循环、条件或嵌套函数中调用Hook
  • 只在React函数中调用Hook,不要在普通的JavaScript函数只能怪调用Hook,可以在React的函数组件中调用或在自定义Hook中调用

    第3章:React应用(基于React脚手架)

    3.1 使用脚手架工具创建React应用

    3.1.1 脚手架介绍

    React官网推荐使用 Create React App 创建一个新的单页应用。如果是大型复杂的前端项目,我们也可以自己从0开始搭建——脚手架工具包含三大组件:package管理器、打包器、编译器,所以我们可以用npm + webpack + babel组件自己的工具。
    脚手架工具可以给我们带来的便利包括:

  • 拓展项目和组件的规模,以工程化的思维管理项目,模块化、组件化、工程化
  • 包含了项目所需的基本配置
  • 指定好了基础依赖
  • 提供了一个最简单demo可以直接查看效果
  • 清晰的文件管理目录

    3.1.2 创建项目并启动

    Create React App 会配置你的开发环境,以便使你能够使用最新的 JavaScript 特性,提供良好的开发体验,并为生产环境优化你的应用程序。

    npx create-react-app my-app
    cd my-app
    npm start
    3.1.3 理解项目结构

    3.2 demo

    第4章:开发中若干要点

    4.1 组件间通信

    4.1.1 方式一:通过props传递
  • 共同的数据放在父组件上,特有的数据放在内部作为 state 维护
  • 通过 props 可以传递一般数据和函数,一层一层传递

    一般数据是由父组件传递给子组件,子组件进行数据读取,而函数经常被用来在子组件中调用并传递数据给父组件。

    4.1.2 方式二:使用消息发布订阅机制

    可以借助PubSub库来实现

    4.1.3 方式三: Context

    Props 属性是自上而下传递的,但当一个属性是应用程序中许多组件都需要的时候,显式地通过组件树层层传递就显得繁琐,Context 提供了一个无需为每层组件手动添加 props ,就能在组件树间进行数据传递的方法。
    Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
    props 代码示例:

    class App extends React.Component {
    render() {
      return <Toolbar theme="dark" />;
    }
    }
    function Toolbar(props) {
    // Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
    // 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
    // 因为必须将这个值层层传递所有组件。
    return (
      <div>
        <ThemedButton theme={props.theme} />
      </div>
    );
    }
    class ThemedButton extends React.Component {
    render() {
      return <Button theme={this.props.theme} />;
    }
    }

    Context 代码示例

    // 使用Context
    // Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
    // 为当前的 theme 创建一个 context(“light”为默认值)。
    const ThemeContext = React.createContext('light');
    class App extends React.Component {
    render() {
      // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
      // 无论多深,任何组件都能读取这个值。
      // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
      return (
        <ThemeContext.Provider value="dark">
          <Toolbar />
        </ThemeContext.Provider>
      );
    }
    }
    // 中间的组件再也不必指明往下传递 theme 了。
    function Toolbar() {
    return (
      <div>
        <ThemedButton />
      </div>
    );
    }
    class ThemedButton extends React.Component {
    // 指定 contextType 读取当前的 theme context。
    // React 会往上找到最近的 theme Provider,然后使用它的值。
    // 在这个例子中,当前的 theme 值为 “dark”。
    static contextType = ThemeContext;
    render() {
      return <Button theme={this.context} />;
    }
    }

    注意事项:

  • Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。

    4.1.4 方式四:Redux

    Redux提供一种全局的应用程序状态管理工具,可以像 vue 中的 vuex 一样方便地管理共享状态。后面会详细介绍。

    4.2 异步请求

    React本身只关注于界面,并不包含 ajax 请求,当需要前后台交互时,需要集成第三方 ajax 库。
    axiosfetch 是较为常用的 ajax 请求库:
    axios : 封装了 XmlHttpRequest 对象的 ajax 的轻量级工具,支持 promise ,浏览器端和服务端均可使用
    fetch : 是新版浏览器支持的原生函数,老版本浏览器并不支持,需要支持老版本的话可添加 fetch.js ,不再使用 XmlHttpRequest 对象提交 ajax

    4.3 ES6及TypeScript

    ES6: 由于React类组件是采用 ES6 class 定义的,所以我们需要在一开始就配合 babel 使用。
    当项目越来越大时,增加静态类型检查器可以在运行前识别某些类型错误。React中提供了 PropTypes 来限定 props 的输入类型,但其实在函数输入输出、 state 管理、接口统一规范等各处都需要进行静态类型检查。
    建议在大型代码库中使用 FlowTypeScript 来代替,尤其是 TypeScript

    4.4 开源UI组件库

  • material-ui 国外的一款开源react UI组件库
  • ant-design 由国内蚂蚁金服打造的react UI组件库, 主要用于研发企业级中后台产品,同时提供移动端版本 ant-design-mobile

    注意:在使用UI组件库时,尽量采用按需加载的方式打包组件,不要讲UI组件库一次性全盘导入,不利于缩小打包体积。

    第5章:react-router

    5.1 基本概念

    5.1.1 react-router 与 react-router-dom

    react-router 是react的一个专门用于实现SPA应用的插件库,基于react的项目基本上都会用到此库,它实现了路由的核心功能。目前我们使用的是 react-router5 的版本。react-router-dom 则是基于 react-router 并加入了在浏览器运行环境下的一些功能,例如 Link 组件、BrowserRouterHashRouter 组件。

    5.1.2 路由分类

    路由是一种映射关系,key 为路由路径, valuefunction/component

  • 后台路由 服务器路由,valuefunction ,用于处理用户提交的请求并返回响应数据
  • 前台路由 浏览器端路由,value component ,当请求路由path匹配时,界面会更新显示对应的路由,没有发生http请求

    5.1.3 路由组件分类
  • 根组件:BrowserRouter(history模式) 和 HashRouter(hash模式)
    BrowserRouter(history模式) 和 HashRouter(hash模式)作为路由配置的最外层容器,是两种不同的模式,可根据需要选择。
  • 路径匹配组件: RouteSwitch
  • 导航组件: LinkNavLink

    5.2 history 库

    history 库是 react-router 包的唯二依赖,另一个是react本身。这个库管理浏览器会话历史工具库,有三种模式:

    • browser history, 基于 H5 History Api 封装
    • hash history 基于 window.location.hash 封装
    • memory history 在no-DOM环境中适用,比如RN

    history 对象提供了和 H5 History Api非常相似的属性和方法

  • length
  • action
  • loction( pathname | search | hash | state )
  • push(path, [state])
  • replace(path, [state])
  • go(n)
  • goBack()
  • goForward()
  • block(prompt)

    5.3 react-router 相关API

    5.3.1 路由组件 BrowserRouter 和 HashRouter

    BrowserRouter(history模式) 和 HashRouter(hash模式)作为路由配置的最外层容器,是两种不同的模式,可根据需要选择。

    // history 模式:
    class App extends Component {
    render() {
      return (
        <BrowserRouter>
          <Header />
          <Route path='/' exact component={Home}></Route>
          <Route path='/login' exact component={Login}></Route>
          <Route path='/detail/:id' exact component={Detail}></Route>
        </BrowserRouter>
      )
    }
    }
    // hash 模式:
    class App extends Component {
    render() {
      return (
        <HashRouter>
          <Header />
          <Route path='/' exact component={Home}></Route>
          <Route path='/login' exact component={Login}></Route>
          <Route path='/detail/:id' exact component={Detail}></Route>
        </HashRouter>
      )
    }
    }
    5.3.2 路径匹配组件: Route 和 Switch
  • Route: 用来控制路径对应显示的组件
    参数:
    path: 指定跳转路径
    exact: 精确匹配路由
    component: 路由对应组件
    render: 通过写render函数返回具体的dom或组件
    location: 与当前历史记录位置以外的位置相匹配,则此功能在路由过渡动效中非常有用
    sensitive:是否区分路由大小写
    strict: 是否配置路由后面的 '/'
  • Switch: 渲染与该地址匹配的第一个子节点 <Route> 或者 <Redirect>。类似于选项卡,只是匹配到第一个路由后,就不再继续匹配

    <Switch> 
        <Route path='/home'  component={Home}></Route>
        <Route path='/login'  component={Login}></Route> 
        <Route path='/detail' exact  component={detail}></Route> 
        <Route path='/detail/:id'  component={detailId}></Route> 
        <Redirect to="/home" from='/' /> 
    </Switch>

    注意上述的 path='/detail/:id',当路由为 /detail/1 时,若不加 exact ,则只会匹配渲染 path='/detail' 路由,添加只会,才会正确地渲染 path='/detail/:id' 路由。

    5.3.3 导航组件: Link 和 NavLink

    LinkNavLink 都可以用来指定路由跳转, NavLink 的可选参数更多。 NavLink 可以看做 一个特殊版本的 Link ,当它与当前 URL 匹配时,为其渲染元素添加样式属性。

    <Link to='/login' activeClassName="selected">
      <span>登录</span>
    </Link>
    <NavLink
    to="/login"
    activeStyle={{
      fontWeight: 'bold',
      color: 'red'
     }}
    >
      <span>登录</span>
    </NavLink>
    5.3.4 导航组件: Redirect

    <Redirect> 将导航到一个新的地址。即重定向。

    <Switch> 
      <Route path='/home' exact component={Home}></Route>
      <Route path='/login' exact component={Login}></Route> 
      <Redirect to="/home" from='/' exact /> 
    </Switch>

    上面,当访问路由 / 时,会直接重定向到 /home
    <Redirect> 常用语用户登录

    class Center extends PureComponent {
      render() {
          const { loginStatus } = this.props;
          if (loginStatus) {
              return (
                  <div>个人中心</div>
              )
          } else {
              return <Redirect to='/login' />
          }
      }
    }
    5.3.5 其他
  • withRouter 函数
    withRouter 函数可以将一个非路由组件包裹为 withRouter 高阶路由组件,使这个非路由组件也能访问到当前路由的 match, location, history 对象。不论组件何时渲染, withRouter 总会将最新的 match, location, history 通过 props 传递给这个被封装的组件。

    import React from "react";
    import PropTypes from "prop-types";
    import { withRouter } from "react-router";
    
    // A simple component that shows the pathname of the current location
    class ShowTheLocation extends React.Component {
      static propTypes = {
        match: PropTypes.object.isRequired,
        location: PropTypes.object.isRequired,
        history: PropTypes.object.isRequired
      };
    
      render() {
        const { match, location, history } = this.props;
    
        return <div>You are now at {location.pathname}</div>;
      }
    }
    
    // Create a new component that is "connected" (to borrow redux
    // terminology) to the router.
    const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
  • match 对象
    match 对象包含了 <Route> 对当前URL的匹配情况,包含以下属性:
    params: 动态路由的 key-value 匹配对
    isExact: 如果精准匹配上了,没有多余的结尾字符,为true
    path: path匹配表达式, <Route> 时有用
    url: url 匹配串, <Link> 时有用

    // 如果获取match对象
    Route component as this.props.match
    Route render as ({ match }) => ()
    Route children as ({ match }) => ()
    withRouter as this.props.match
    matchPath as the return value
    useRouteMatch as the return value
  • location 对象

    {
      key: 'ac3df4', // not with HashHistory!
      pathname: '/somewhere',
      search: '?some=search-string',
      hash: '#howdy',
    [userDefined]: true,
  }
}
```

   我们在使用导航组件时,经常用 path 字符串来表述要跳转的地址,但当我们需要传递额外参数时,location对象就可以发挥作用了。如:


```JavaScript
// usually all you need
<Link to="/somewhere"/>

// but you can use a location instead
const location = {
  pathname: '/somewhere',
  state: { fromDashboard: true }
}

<Link to={location}/>
<Redirect to={location}/>
history.push(location)
history.replace(location)
```

第6章: Redux

6.1 初识Redux

Redux 是专门用于做状态管理的JS库,并不是React的插件库,angular\vue中也可以使用。它可以集中式管理 React 应用的多个组件共享状态。当应用中多个组件需要共享并管理某些状态时,可以用到Redux。比如应用的主题theme管理、音乐类应用的当前歌曲列表管理等。
redux的工作流程如下:
redux工作流程

6.2 redux的核心API

6.2.1 createStore()

其作用是创建包含指定 reducerstore 对象

import { createStore } from 'redux'
import myReducer from 'mypathtoreducer'
const store = createStore(myReducer)
6.2.2 store对象

store 是 Redux 库最核心的管理对象,它内部维护 statereducer ,提供 getState() dispatch(action) subscribe(listener) 三个核心方法用于管理状态

6.2.3 applyMiddleware()

applyMiddleware() 是应用于 Redux 的中间件

import { createStore } from 'redux'
import thunk from 'redux-thunk' // 以异步中间件为例
import myReducer from 'mypathtoreducer'
const store = createStore({
  myReducer,
  applyMiddleware(thunk) // 应用异步中间件
})
6.2.4 combineReducers()

combineReducers() 的作用是合并多个 reducer 函数

export default combineReducers({
  reducerA,
  reducerB
  ...
})

6.3 redux的三个核心概念

6.3.1 action

action 是要执行的行为对象,包含 type 标志属性和行为数据属性,如:

const ADD_POST = 'APP/ADD_POST'

const action = {
  type: ADD_POST,
  payload: {
    post: { content: '111' }
  }
}

// 创建 action 的工厂函数会返回一个action
const addPost = (post) => ({
  type: ADD_POST,
  payload: {
    post: { content: '111' }
  }
})
6.3.2 reducer

reducer 根据老的 stateaction ,进行处理,并返回新的 state

export default function addPost(initialState = [], action) {
  switch (action.type) {
    case ADD_POST:
      return [].concat(action.payload.post).concat(initialState)
    default:
      return initialState
  }
}
6.3.3 store

store 是将 stateactionreducer 联系到一起的对象

import { createStore } from 'redux'
import myReducer from 'mypathtoreducer'
const store = createStore(myReducer)

store 提供如下api供我们管理状态:

  • getState() 获取 state
  • dispatch(action) 分发 action , 触发 reducer ,即可改版 state
  • subscribe(listener) 注册监听,当产生新的 state 时自动调用

    6.3.4 redux的问题

    查看示例后,我们发现:

  • Redux 与 React 组件的代码耦合度太高
  • 编码不够简洁

6.4 react-redux

6.4.1 初始 react-redux

react-redux 是专门用于简化 react 应用中的 redux 使用情况的一个 react 插件库。其将组建分为两类:

  1. UI 组件:只负责从 props 中接收数据并进行 UI 的呈现,而不带有任何业务逻辑,不需要使用到 Redux 的API
  2. 容器组件: 负责管理数据和业务逻辑,不负责 UI 的呈现,会涉及到 Redux 的相关 API

    6.4.2 react-redux 的API
  3. Provider
    让所有被包裹的组件都可以得到state数据

    import { Provider } from 'react-redux'
    
    <Provider store={store}>
      <APP />
    </Provider>
  4. connect()
    connect() 方法用于包装 UI 组件生成容器组件

    import { connect } from 'react-redux'
    
    export default connect(mapStateToProps, mapDispatchToProps)(Counter)
  5. mapStateToProps()
    将外部的数据(即 state 对象)转换为UI组件的 props 属性,这样在组件内部想要获取 redux 中的 state 时,调用 props 即可
  6. mapDispatchToProps()
    将分发 action 的函数转换为 UI 组件的 props 属性,可以在组件内通过调用 props 方法属性的形式分发 action

    import { connect } from 'react-redux'
    
    class Counter extends React.Component {
      componentWillMount () {
        this.props.onLoad()
      }
      render () {
        let value = this.props.value
        return (...)
      }
    }
    
    const mapStateToProps = (state) => ({
      value: state.value
    })
    const mapDispatchToProps = (dispatch) => ({
      onLoad: addPost
    })
    export default connect(mapStateToProps, mapDispatchToProps)(Counter)
    6.4.3 存在的问题

    Redux 默认是不能进行一步处理的, 当应用中确实需要在 Redux 中执行异步任务时,可以选择合适的插件来实现,如 saga redux-thunk


穿职业装的程序媛
32 声望4 粉丝