React前端入门培训指南04——React基础

 学习 React  原创  管理员  2020-03-08 19:43

概要:我们需要打造出前端开发所需要的一切基础功能,后续开发者,只需要简单地引用并使用此功能即可,只有这样才能加快开发速度。以前我们习惯了 jQuery 时代的灵活性,觉得对 DOM 的操作简直是恶梦,针对某一个功能点,习惯性地先到网上找相关插件,再引用此 js 文件至项目中,对于不同版本的兼容与更新维护更是没有概念。

背景

当今的 web 页面交互越来越复杂,为了适应业务的发展,企业不得不加快产品的迭代周期。前端开发者需要灵活调整自己的模块逻辑,其中最基本的要求就是最大程度上减少造轮子所消耗的时间。

我们需要打造出前端开发所需要的一切基础功能,后续开发者,只需要简单地引用并使用此功能即可,只有这样才能加快开发速度。以前我们习惯了 jQuery 时代的灵活性,觉得对 DOM 的操作简直是恶梦,针对某一个功能点,习惯性地先到网上找相关插件,再引用此 js 文件至项目中,对于不同版本的兼容与更新维护更是没有概念。

现在,我们使用 React 来减少项目的开发成本,再加上已经非常流行的 Node.js 和 NPM 的帮助,更是加快了前端开发的步伐。

解决的问题

  1. 如何提高代码的复用能力?
  2. 如何高效地更新 DOM 内容?
  3. 如何减少或完全杜绝对 DOM 的操作?

基本概念

用于构建用户界面的 JavaScript 库

术语

JSX

混合 HTML 和 JavaScript 的一种语法。

示例:

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

组件(component)

一个拥有完整独立功能的代码块,类似于传统开发中的插件,这里的组件可以是非常细粒度的功能点。

示例:

class HelloMessage extends React.Component {
  render() {
    return (
      <div>
        Hello
      </div>
    );
  }
}

HelloMessage 就是一个组件。

属性(props)

从组件外部传递给此组件的一些数据,相当于组件开放了一些属性作为与外部交互的窗口。

示例:

<HelloMessage name="Taylor" />

name 就是组件 HelloMessage 的属性。

状态(state)

由于 React 组件不应该修改传入的 props,因此使用 state 来描述组件内部的数据变化情况,这些变化对组件外部来说是不透明的。

示例:

class Timer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { seconds: 0 };
  }
 
  tick() {
    this.setState(state => ({
      seconds: state.seconds + 1
    }));
  }
 
  componentDidMount() {
    this.interval = setInterval(() => this.tick(), 1000);
  }
 
  componentWillUnmount() {
    clearInterval(this.interval);
  }
 
  render() {
    return (
      <div>
        Seconds: {this.state.seconds}
      </div>
    );
  }
}

纯函数(Pure function)

给一个 function 相同的参数,永远会返回相同的值,并且没有副作用;在 React 中,就是给一个组件相同的 props, 永远渲染出相同的视图,并且没有其他的副作用;纯组件的好处是,容易监测数据变化、容易测试、提高渲染性能等。

副作用(Side Effect)

一个 function 做了和本身运算返回值无关的事,比如:修改了全局变量、修改了传入的参数、甚至是 console.log(),ajax 操作、修改 dom 都是属于副作用操作。

特点

  1. React 使创建交互式 UI 变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据改变时 React 能有效地更新并正确地渲染组件。
  2. 创建拥有各自状态的组件,再由这些组件构成更加复杂的 UI。组件逻辑使用 JavaScript 编写而非模版,因此你可以轻松地在应用中传递数据,并使得状态与 DOM 分离。
  3. 无论你现在正在使用什么技术栈,你都可以随时引入 React 来开发新特性,而不需要重写现有代码。React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用。

理念

UI = render(data)

UI:我们看到的网页界面。

render:React 封装好的功能。

data:React 界面上需要显示的数据源。

在使用 jQuery 时,我们需要清楚具体地告诉 jQuery 第一步怎么做,第二步怎么做,如何设置属性、html等。而在 React 这里,只需要关注 data 本身即可,界面元素的渲染工作由 React 自行搞定。

在 React 开发中,要想更新界面,只需要更新 data 即可,这也是所谓 “响应式编程” 的思想。

虚拟 DOM

为什么需要它

当 data 发生变化时,我们需要调用 render 来更新用户界面。然而,如果只是 data 中的某一小部分内容发生改动,却要调用 render 来刷新整个组件,未免做了一些无用功。毕竟,在 jQuery 的开发方式下我们只需要操作需要改动的 DOM 元素即可,其它元素可以保持不动。因此,React 引入了虚拟 DOM 的机制来保证生成 UI 时的效率。

React 使用虚拟 DOM,让每次渲染都只更新最少的 DOM 元素。

概念

Virtual DOM 是一种编程概念。在这个概念里,UI 以一种理想化的,或者说“虚拟的”表现形式被保存于内存中,并通过如 ReactDOM 等类库使之与“真实的” DOM 同步。这一过程叫做协调。

Virtual DOM 是真实 DOM 的模拟,真实 DOM 是由真实的 DOM 元素构成,Virtual DOM 也是由虚拟的 DOM 元素构成。真实 DOM 元素我们已经很熟悉了,它们都是 HTML 元素(HTML Element)。那虚拟 DOM 元素是什么呢?React 给虚拟 DOM 元素取名叫 React 元素(React Element)。

在 JSX 的写法中,不论是 HTML 原生元素,例如:<p></p>,<div></div>,等。或者自定义的组件,如 <Message /> 等。它们都是 React Element,而创建这些 Element 的方法就是 React.createElement。

差异比较

React 会将 state 的多次修改进行合并,计算出最终需要更新的具体 DOM 有哪些,然后再将这些 DOM 的修改一次性更新到界面中。从而避免多次更新 DOM 带来的性能损耗。

UI 组成

在开始编码之前,我们需要对界面进行组件拆分,开发者要清楚地知道哪些模块可以独立出来,哪些模块可以有包含关系作为一个整体,哪些模块可以做到完全通用等。

这一步是非常重要的环节,如果这一步做得不够好,后续可能会经常返工或重构。

组件构成

class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}
 
// 用法示例: <ShoppingList name="Mark" />
  1. ShoppingList 是一个 React 组件类,或者说是一个 React 组件类型。
  2. 一个组件接收一些参数,我们把这些参数叫做 props(“props” 是 “properties” 简写),然后通过 render 方法返回需要展示在屏幕上的视图的层次结构。
  3. render 方法的返回值描述了你希望在屏幕上看到的内容。React 根据描述,然后把结果展示出来。更具体地来说,render 返回了一个 React 元素,这是一种对渲染内容的轻量级描述。
  4. 每个组件都是封装好的,并且可以单独运行,这样你就可以通过组合简单的组件来构建复杂的 UI 界面。

如果不使用 JSX 语法,可以使用与此效果相同的写法:

React.createElement("div", {
  className: "shopping-list"
}, React.createElement("h1", null, "Shopping List for ", props.name), React.createElement("ul", null, React.createElement("li", null, "Instagram"), React.createElement("li", null, "WhatsApp"), React.createElement("li", null, "Oculus")));

组件数据

props 是组件对外的数据接口(公开的),state 是组件的内部状态(私有的)。总之,对外使用 props ,对内使用 state 。

props

创建组件 App:

class App extends React.Component {
  render() {
    return (
      <button className="btn">
        {this.props.value}
      </button>
    );
  }
}

调用此组件:

<App value={"提交"} />

每一次调用组件 App,就是创建了一个组件 App 的实例,在调用时设置的 value 会传给组件实例中的 this.props 属性中。因此,我们可以根据 this.props.value 来读取传过来的值。

state

state 存在的目的是存储组件内部的数据更新。

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 0
    };
  }
 
  render() {
    return (
      <button
        className="btn"
        onClick={() => this.setState({value: this.state.value + 1 })}
      >
        {this.state.value}
      </button>
    );
  }
}

每次单击时,state.value 值会加1,当 React 检测到 state 中的 value 有变化时,会自动重新更新此组件对应的 DOM 元素,这样我们在界面上就会看到 button 的文本有更新。

注意,我们更新 state 时,一定要使用 setState 方法来更新 state 中的内容,而不是直接修改 state 值。因为每次在组件中调用 setState 时,React 都会自动更新其子组件。

这种写法是错误的,它并不会触发组件的重新渲染:

this.state.value += 1 ;

注意:我们只可以在构造函数中直接修改 this.state,在构造函数中,相当于设置当前组件 state 的初始值。

this.setState 可能是异步的,因此,我们不能按同步的方式去写。如:

this.setState({value:100})
if(this.state.value == 100){
    //这里的代码可能并不会执行
}

当使用 setState 更新 state 后,并不会同步式地修改当前的 state 值,而是异步修改 state 的值,再驱动界面的更新。因此,后面直接获取 this.state.value 的值是不准确的。

可以通过 callback 参数来获取更新后的 state 值:

this.setState({value: 100}, () => {
  console.log(this.state.value == 100)// true
})

多次调用 setState :

  • 当 setState() 传对象类型参数,React会合并重复多次调用,并触发一次 render。

  • 当 setState() 传函数类型参数,React会依次调用函数,并触发一次 render。

可以看到,即使我们多次重复调用 setState(),不管是传参是何种类型。React都只会调用一次 render,重新渲染组件。这也是性能优化的一部分,防止多次重复渲染带来的性能问题。

官网推荐我们使用 setState() 时候,第一个参数传函数类型参数,因为函数参数中接收的 state 和 props 都为最新的数据。

组件生命周期

http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

挂载

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

constructor(props)

初始化 state:如果不需要初始化,可以不用设置 state。

绑定成员函数的 this 环境:因为在 ES6 中,class 的成员函数中的 this 并不会默认指向当前实例。

static getDerivedStateFromProps(props, state)

会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

render()

组件中唯一必须实现的方法。

render() 函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。

componentDidMount()

会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。

更新

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

static getDerivedStateFromProps(props, state)

同上

shouldComponentUpdate(nextProps, nextState)

当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用,以引来通知 React 是否触发更新。返回值默认为 true。首次渲染或使用 forceUpdate() 时不会调用该方法。

此方法仅作为性能优化的方式而存在。

render()

同上

getSnapshotBeforeUpdate(prevProps, prevState)

在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。

componentDidUpdate()

会在更新后会被立即调用。首次渲染不会执行此方法。

卸载

当组件从 DOM 中移除时会调用如下方法:

componentWillUnmount()

会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

错误处理

当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

static getDerivedStateFromError(error)

会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state。

componentDidCatch(error, info)

此生命周期在后代组件抛出错误后被调用。

示例

我们使用不同的方式来实现如下功能:

使用 jQuery

https://codesandbox.io/s/quirky-brahmagupta-4j9bl

<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="UTF-8" />
    <script
      src="https://code.jquery.com/jquery-3.4.1.min.js"
      integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
      crossorigin="anonymous"
    ></script>
    <script src="https://unpkg.com/art-template/lib/template-web.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <br />
    <br />
    <div>当前已选ID为:<span id="spanSelectedId">空</span></div>
  </body>
  <script type="text/html" id="tpl">
    <table>
      <tr>
        <td>
          <input type="checkbox" class="ckAll"/>
        </td>
        <td>ID</td>
        <td>Name</td>
        <td>Age</td>
      </tr>
      {{each data m i}}
        <tr bgcolor="{{i%2==0?'#f60':''}}">
          <td>
            <input type="checkbox" class="ckItem" value="{{m.id}}"/>
          </td>
          <td>{{m.id}}</td>
          <td>{{m.name}}</td>
          <td>{{m.age}}</td>
        </tr>
      {{/each}}
    </table>
  </script>
  <script type="text/javascript">
    $(function() {
      //渲染界面
      var dataSource = [
        {
          id: 1,
          name: "AA",
          age: 10
        },
        {
          id: 2,
          name: "BB",
          age: 20
        },
        {
          id: 3,
          name: "CC",
          age: 5
        },
        {
          id: 4,
          name: "DD",
          age: 15
        }
      ];
      var tplHTML = $("#tpl").html();
      var resultHTML = template.render(tplHTML, {
        data: dataSource
      });
      $("#app").html(resultHTML);
      //绑定事件
      var $ckAll = $(".ckAll"),
        $ckItem = $(".ckItem");
 
      //获取已选信息
      var getSelectedIds = function() {
        var result = [];
        $ckItem.each(function(idx, item) {
          item.checked && result.push(item.value);
        });
        return result;
      };
 
      var updateSelected = function() {
        $("#spanSelectedId").html(getSelectedIds().join(",") || "空");
      };
 
      //全选、全不选
      $ckAll.on("click", function() {
        $ckItem.each(function() {
          $(this).prop("checked", $ckAll.prop("checked"));
        });
        updateSelected();
      });
 
      //选择子项
      $ckItem.on("click", function() {
        var isAllSelected = true;
        $ckItem.each(function(idx, item) {
          if (!item.checked) {
            isAllSelected = false;
          }
        });
        $ckAll.prop("checked", isAllSelected);
        updateSelected();
      });
    });
  </script>
</html>

使用 React

https://codesandbox.io/s/happy-microservice-jz4it

import React from "react";
 
const dataSource = [
  {
    id: 1,
    name: "AA",
    age: 10
  },
  {
    id: 2,
    name: "BB",
    age: 20
  },
  {
    id: 3,
    name: "CC",
    age: 5
  },
  {
    id: 4,
    name: "DD",
    age: 15
  }
];
 
export default class extends React.Component {
  constructor(props) {
    super(props);
    this.onCKAllClick = this.onCKAllClick.bind(this);
    this.state = {
      isAllChecked: false,
      data: this.props.dataSource.map(d => {
        return {
          id: d.id,
          isChecked: false
        };
      })
    };
  }
  static defaultProps = {
    dataSource: dataSource
  };
  onCKAllClick(e) {
    const newState = { ...this.state };
    newState.isAllChecked = e.target.checked;
    newState.data = this.state.data.map(d => {
      return {
        ...d,
        isChecked: e.target.checked
      };
    });
    this.setState(newState);
  }
  onCKItemClick(id, e) {
    const newState = { ...this.state };
    newState.data = this.state.data.map(d => {
      if (d.id !== id) {
        return d;
      }
      return {
        ...d,
        isChecked: e.target.checked
      };
    });
    newState.isAllChecked = newState.data.find(k => !k.isChecked)
      ? false
      : true;
    this.setState(newState);
  }
  render() {
    return (
      <div>
        <table>
          <tr>
            <td>
              <input
                type="checkbox"
                onChange={this.onCKAllClick}
                checked={this.state.isAllChecked}
              />
            </td>
            <td>ID</td>
            <td>Name</td>
            <td>Age</td>
          </tr>
 
          {this.props.dataSource.map((data, idx) => {
            return (
              <tr key={data.id} bgcolor={idx % 2 === 0 ? "#f60" : ""}>
                <td>
                  <input
                    type="checkbox"
                    onChange={this.onCKItemClick.bind(this, data.id)}
                    value={data.id}
                    checked={
                      this.state.data.find(k => k.id === data.id).isChecked
                    }
                  />
                </td>
                <td>{data.id}</td>
                <td>{data.name}</td>
                <td>{data.age}</td>
              </tr>
            );
          })}
        </table>
        <br />
        <div>
          当前已选ID为:
          {this.state.data
            .filter(k => k.isChecked)
            .map(k => k.id)
            .join(",") || "空"}
        </div>
      </div>
    );
  }
}

参考资料

https://react.docschina.org/docs/getting-started.html

React  web  html  

编辑:myweb   最后更新于:2020-03-08 20:05


版权声明:本站所有原创内容受到法律的严格保护,版权归本站所有。如果您觉得我们的内容有价值,您仍然可以进行自由传播,但必须显著地标识或说明此内容在本站的链接地址。




联系我:help@wodeabc.com,鄂ICP备14016278号-2
©2016-2020 我的ABC All Rights Reserved.
友情链接: 一起编程网