3.精通React-React基础入门-《前端知识进阶》

admin 2025-11-01 15:32:08 编程 来源:ZONE.CI 全球网 0 阅读模式
  • 一、开发前的准备
    • 1. 开发环境
    • 2. 开发工具
    • 3. 开发依赖
  • 二、React脚手架
  • 三、创建React项目
  • 四、React初体验
  • 五、初识JSX
    • 1. 什么是JSX
    • 2. 为什么使用JSX
    • 3. JSX书写规范
    • 4. JSX注释
    • 5. JSX嵌入变量
    • 6. JSX嵌入表达式
    • 7. JSX绑定属性
    • 8. JSX绑定事件
    • 9. React条件渲染
    • 10. React列表渲染
    • 11. React表单处理
  • 六、JSX是如何变成DOM的?
  • 七、案例练习

    一、开发前的准备

    1. 开发环境

    (1)Node.jsNode.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。React 应用并不依赖于 Node.js 运行,但是开发过程中的一些编译过程(比如 npm,Webpack 等)都需要在 Node 环境下运行。因此,开发 React 应用前,应确保已经安装了 Node.js。(2)NPMNPM 是 Node 的一个包管理工具,每个包都是一个模块,能够使你轻松下载、管理模块依赖和版本。同样的,在使用 React 开发应用时,会依赖很多模块,这些模块就可以通过 NPM 进行下载。由于 NPM 已集成到了 Node.js 中,因此不用单独下载。

    2. 开发工具

    (1)WebpackWebpack 是一个前端资源加载和打包工具。Webpack 提供了模块化的开发方式,将各种静态资源视为模块,如 JavaScript、CSS、图片等,并通过 Webpack 生成优化过的代码。同样在开发 React 应用时也要用到 Webpack 来进行模块打包。(2)BabelBabel 是一个 JavaScript 编译器,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。由于我们在开发 React 应用中,会用到很多 ES6 的语法,但目前浏览器并不完全支持,因此在 Webpack 编译阶段,利用 Babel 将 ES6 及其以后的语法编译成 ES5 语法。

    3. 开发依赖

    开发React必须依赖三个库:

    • react:包含react所必须的核心代码
    • react-dom:react渲染在不同平台所需要的核心代码
    • babel:将jsx转换成React代码的工具

    react-dom针对web和native所完成的事情不同:

    • web端:react-dom会讲jsx最终渲染成真实的DOM,显示在浏览器中
    • native端:react-dom会讲jsx最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton)。

    Babel ,又名 Babel.js,是目前前端使用非常广泛的编辑器、转移器。比如当下很多浏览器并不支持ES6的语法,但是确实ES6的语法非常的简洁和方便,我们开发时希望使用它。那么编写源码时我们就可以使用ES6来编写,之后通过Babel工具,将ES6转成大多数浏览器都支持的ES5的语法。

    React和Babel的关系:

    • 默认情况下开发React其实可以不使用babel。
    • 但是前提是我们自己使用 React.createElement 来编写源代码,它编写的代码非常的繁琐和可读性差。
    • 那么我们就可以直接编写jsx(JavaScript XML)的语法,并且让babel帮助我们转换成React.createElement。

      二、React脚手架

      现代的前端项目已经越来越复杂了:

    • 不会再是在HTML中引入几个css文件,引入几个编写的js文件或者第三方的js文件这么简单;

    • 比如css可能是使用less、sass等预处理器进行编写,我们需要将它们转成普通的css才能被浏览器解析;
    • 比如JavaScript代码不再只是编写在几个文件中,而是通过模块化的方式,被组成在成百上千个文件中,我们需要通过模块化的技术来管理它们之间的相互依赖;
    • 比如项目需要依赖很多的第三方库,如何更好的管理它们(比如管理它们的依赖、版本升级等);

    为了解决这些问题,前端脚手架就出现了。例如babel、webpack、gulp等等,可以通过它们来进行转换规则、打包依赖、热更新等等。总之,脚手架让项目从搭建到开发,再到部署,整个流程变得快速和便捷。

    目前比较流行的的框架Vue,它的脚手架是vue-cli;React的脚手架就是create-react-app。它们的作用就是帮助我们生成一个通用的目录结构,并且已经将我们所需的工程环境配置好了。目前,这些脚手架都是使用node.js编写的,并且都是基于Webpack的,它们需要运行在Node环境下。

    上面我们也说到了NPM了,它是 Node 的一个包管理工具,每个包都是一个模块,能够使你轻松下载、管理模块依赖和版本。同样的,在使用 React 开发应用时,会依赖很多模块,这些模块就可以通过 NPM 进行下载。由于 NPM 已集成到了 Node.js 中,因此不用单独下载。

    除了NPM,还有一个比较出名的Node包管理工具yarn,它是为了弥补NPM的一些缺陷而出现的,早期的NPM存在很多的缺陷,比如按安装依赖速度很慢,版本依赖混乱等等一系列的问题,虽然后面的版本进行了很多的升级和改进,但还是很多人喜欢使用yarn,React脚手架也默认使用yarn。

    安装yarn:

    1. npm install -g yarn

    安装好yarn就可以使用了,下面来看一下npm和yarn命令的对照表:image.png安装完之后,就可以安装React的脚手架了:

    1. npm install -g create-react-app

    安装好之后,可以运行:create-react-app --version 查看版本号,如果版本号正常展示,证明安装成功。

    三、创建React项目

    这里我们借助上面所说的的React脚手架 Create React App 创建 React 应用。该脚手架已经将 Webpack、Babel 等工具的配置做了封装,无需开发者做配置,提供了一个零配置的现代构建。

    Create React App 对于开发环境版本有一定的要求,具体如下: npm 版本 >= 5.6 node 版本 >= 8.10

    快速搭建 React 应用需要三个步骤:

    1. 创建 React 项目;
    2. 启动项目;
    3. 暴露配置项。

    下面我们就来逐步操作,创建一个React项目:(1)创建 React 项目

    1. create-react-app my-demo

    这里项目名不能使用驼峰的形式(即不能包含大写字母),不然会有以下报错:name can no longer contain capital letters

    (2)启动项目

    1. cd my-demo
    2. npm start

    当看到以下界面时,说明你的 React 应用就已经安装好了。image.png

    1. ├── README.md 文档
    2. ├── package.json npm 依赖信息
    3. ├── package-lock.json 依赖模块的版本信息
    4. ├── .gitignore 忽略打包的文件
    5. ├── public 静态资源文件夹
    6. │ ├── favicon.ico 网站icon图标
    7. │ ├── index.html 模版
    8. │ ├── logo192.png 192*192大小的react logo
    9. │ ├── logo512.png 512*512大小的react logo
    10. │ ├── manifest.json 移动桌面快捷方式配置文件
    11. │ └── robots.txt 网站与爬虫间的协议
    12. ├── src 源码文件夹
    13. │ ├── App.css
    14. │ ├── App.js 根组件
    15. │ ├── App.test.js
    16. │ ├── index.css 全局样式
    17. │ ├── index.js 入口文件
    18. │ ├── logo.svg
    19. │ └── serviceWorker.js PWA 支持
    20. └── node_modules 依赖包

    在src文件夹有一个serviceWorker.js 文件,在public文件有一个manifest.json文件,他们涉及到了一个知识点——PWA,PWA全称为Progressive Web App,即渐进式WEB应用;一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用;添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能;这种Web存在的形式,我们也称之为是 Web App。

    PWA解决了哪些问题呢?

    • 可以添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏;
    • 实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能;
    • 实现了消息推送;

    还有类似一系列类似于Native App相关的功能。(3)暴露配置项**

    1. npm run eject

    在初始化好的项目中,Webpack等配置默认都是隐藏的,要想看到这些配置,就要执行以上命令来暴露项目的配置项。

    暴露配置项之后,项目根目录又多出两个文件:scripts和config,他们都是被隐藏的配置项,同时package.json中的配置项也会变多,一些隐藏的配置项都暴露了出来。

    四、React初体验

    下面我们就来用react 实现一下Hello World:

    1. 打开 src/index.js 文件,可以看到 render 的模版是 App.js,代码如下:
    2. import React from 'react' // 使用JSX语法必须引入react
    3. import ReactDOM from 'react-dom'
    4. import App from './App.js'
    5. ReactDOM.render(<App />),
    6. document.getElementById('root'))

    其中,root 是 index.html 模版里的元素,渲染出来的 App 组件放在此处。

    下面我们来尝试修改 App.js 文件:

    1. // src/App.js
    2. import React from 'react'
    3. import './App.css'
    4. function App() {
    5. return (
    6. <div className="App">
    7. <header className="App-header">
    8. <p>
    9. hello world
    10. </p>
    11. </header>
    12. </div>
    13. )
    14. }
    15. export default App

    修改完保存之后,页面就会自动刷新,显示如下:image.png至此,我们就完成了一个React应用的创建。

    那React的数据在哪里定义呢,Vue是在data中定义数据,而React是在state中定义数据。

    在组件中的数据,我们可以分成两类:

    • 参与界面更新的数据:当数据变量时,需要更新组件渲染的内容
    • 不参与界面更新的数据:当数据变量时,不需要更新将组建渲染的内容

    参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象的state中

    • 我们可以通过在构造函数中 this.state = {定义的数据}
    • 当我们的数据发生变化时,我们可以调用 this.setState 来更新数据,并且通知React进行update操作
    • 在进行update操作时,就会重新调用render函数,并且使用最新的数据,来渲染界面

      1. class App extends React.Component {
      2. constructor(pops) {
      3. super(props);
      4. this.state = {
      5. message: "hello world!"
      6. }
      7. }
      8. }

      五、初识JSX

      1. 什么是JSX

      我们来看一个变量的声明:

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

      这个变量声明既不是字符串也不是HTML,它被称为JSX,是一种JavaScript的语法扩展(eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法。它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用;

      2. 为什么使用JSX

      那为什么React要选择使用JSX呢?(1) React认为渲染逻辑本质上与其他UI逻辑存在内在耦合

    • 比如UI需要绑定事件(button、a原生等等);

    • 比如UI中需要展示数据状态,在某些状态发生改变时,又需要改变UI;

    (2)它们之间是密不可分,所以React没有讲标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component);

    其实,JSX是嵌入到JavaScript中的一种结构语法;

    JSX的优点:

    • 使用熟悉的语法定义 HTML 元素,提供更加语义化的标签,使用 JSX 编写模板更简单快速;
    • 更加直观:JSX 让小组件更加简单、明了、直观;
    • 抽象了 React 元素的创建过程,使得编写组件变得更加简单;
    • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化;
    • JSX 是类型安全的,在编译过程中就能发现错误;
    • 防注入攻击,所有的内容在渲染之前都被转换成了字符串,可以有效地防止 XSS(跨站脚本) 攻击。

      3. JSX书写规范

      JSX的书写规范如下:

    • JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div原生(或者使用Fragment,文档片段);

    • 为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写;
    • JSX中的标签可以是单标签,也可以是双标签;注意:如果是单标签,必须以/>结尾;
    • 在使用变量时,我们可以将其放在一个大括号中,大括号内放置任何有效的 JavaScript 表达式

    下面来是一个计数器的例子:**

    1. class App extends React.Component {
    2. constructor(pops) {
    3. super(props);
    4. this.state = {
    5. counter: 0
    6. }
    7. }
    8. render() {
    9. return (
    10. <div>
    11. <h2>当前计数: {this.state.counter}</h2>
    12. <button onClick={this.increment}>+1</button>
    13. <button onClick={this.decrement.bind(this)}>-1</button>
    14. <img src="" alt=""/>
    15. </div>
    16. )
    17. }
    18. increment() {
    19. this.setState({
    20. counter: this.state.counter + 1
    21. })
    22. }
    23. decrement() {
    24. this.setState({
    25. counter: this.state.counter - 1
    26. })
    27. }
    28. }
    29. ReactDOM.render(<App/>, document.getElementById("app"));

    4. JSX注释

    JSX的注释和其他框架略有不同,通常有三种方法:

    1. render() {
    2. return (
    3. <div>
    4. {/* 单行注释 */}
    5. {/*
    6. 多行注释
    7. */}
    8. <h2>Hello World</h2> // 行尾注释
    9. </div>
    10. )
    11. }

    5. JSX嵌入变量

    在JSX中嵌入变量时,通常使用一个大括号包裹,不同类型的变量的显示效果是不一样的:(1)情况一:当变量是Number、String、Array类型时,可以直接显示(2)情况二:当变量是null、undefined、Boolean类型时,内容为空;

    • 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
    • 转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;

    (3) 情况三:对象类型不能作为子元素(not valid as a React child)

    1. class App extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. this.state = {
    5. // 1.在{}中可以正常显示显示的内容
    6. name: "why", // String
    7. age: 18, // Number
    8. names: ["abc", "cba", "bac"], // Array
    9. // 2.在{}中不能显示(忽略)
    10. test1: null, // null
    11. test2: undefined, // undefined
    12. test3: true, // Boolean
    13. // 3.对象不能作为jsx的子类
    14. friend: {
    15. name: "zhangsan",
    16. age: 40
    17. }
    18. }
    19. }
    20. render() {
    21. return (
    22. <div>
    23. <h2>{this.state.name}</h2>
    24. <h2>{this.state.age}</h2>
    25. <h2>{this.state.names}</h2>
    26. <h2>{this.state.test1 + ""}</h2>
    27. <h2>{this.state.test2 + ""}</h2>
    28. <h2>{this.state.test3.toString()}</h2>
    29. <h2>{this.state.friend}</h2>
    30. </div>
    31. )
    32. }
    33. }

    6. JSX嵌入表达式

    在JSX中嵌入表达式:

    • 运算表达式
    • 三元运算符
    • 进行函数调用

      1. render() {
      2. // 对state中的变量进行解构赋值
      3. const { firstname, lastname, isLogin } = this.state;
      4. return (
      5. <div>
      6. {/*1.运算符表达式*/}
      7. <h2>{ firstname + " " + lastname }</h2>
      8. <h2>{20 * 50}</h2>
      9. {/*2.三元表达式*/}
      10. <h2>{ isLogin ? "欢迎回来~": "请先登录~" }</h2>
      11. {/*3.进行函数调用*/}
      12. <h2>{this.getFullName()}</h2>
      13. </div>
      14. )
      15. }
      16. getFullName() {
      17. return this.state.firstname + " " + this.state.lastname;
      18. }

      7. JSX绑定属性

      常见的绑定的属性有以下几种:

    • 元素都会有title属性

    • img元素会有src属性
    • a元素会有href属性
    • 元素可能需要绑定class
    • 原生使用内联样式style

    我们可以使用大括号来给元素绑定属性:

    1. class App extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. this.state = {
    5. title: "标题",
    6. imgUrl: "http://p2.music.126.net/L8IDEWMk_6vyT0asSkPgXw==/109951163990535633.jpg",
    7. link: "http://www.baidu.com",
    8. active: true
    9. }
    10. }
    11. render() {
    12. const { title, imgUrl, link, active } = this.state;
    13. return (
    14. <div>
    15. {/* 1.绑定普通属性 */}
    16. <h2 title={title}>我是标题</h2>
    17. <img src={getSizeImage(imgUrl, 140)} alt=""/>
    18. <a href={link} target="_blank">百度一下</a>
    19. {/* 2.绑定class */}
    20. <div className="box title">我是div元素</div>
    21. <div className={"box title " + (active ? "active": "")}>我也是div元素</div>
    22. <label htmlFor=""></label>
    23. {/* 3.绑定style */}
    24. <div style={{color: "red", fontSize: "50px"}}>我是div,绑定style属性</div>
    25. </div>
    26. )
    27. }
    28. }
    29. function getSizeImage(imgUrl, size) {
    30. return imgUrl + `?param=${size}x${size}`
    31. }

    注意:

    • 上面我们在给元素绑定class时,由于我们写的是JSX语法,它是和JavaScript的语法混在一起的,所以我们如果直接使用JavaScript中的关键字class就不太好,所以我们可以使用className来代替class,在label标签中,通常使用的是的for来绑定属性,但是for也是JavaScript中的关键字,所以使用htmlFor来代替。
    • 如果我们想要给className添加值,就要使用JavaScript来给他动态添加。
    • 在给元素绑定style时,需要注意,外层的大括号是用JSX语法往内部嵌套JavaScript代码。而内部的大括号是一个对象,它里面是键值对,表示元素的样式属性及属性值。
    • 在给元素绑定style时,如果元素的属性值是一个值,就要加上双引号。当属性是由多个单词组成的时候,需要用驼峰命名法来表示,例如:fontSize。

      8. JSX绑定事件

      在JSX中绑定事件时,可以使用以下方式:

      1. <button onClick={this.btnClick}>按钮</button>

      可以看出 React 元素的事件处理和 DOM 元素的很相似,但存在一些语法上的差异:

    • React 的事件采用驼峰式命名,而不是纯小写的方式;

    • 使用 JSX 语法时,需要传入一个函数作为事件处理函数,而不是一个字符串;
    • 使用函数时不能加括号,不然会直接执行。

    需要注意的是,如果我们绑定的事件函数中,如果使用到了当前组件对象中的属性,我们无法使用this直接获取state中的数据。这是因为,我们当前调用的函数,不是在元素上直接调用它的,而是React的内部发现这个按钮绑定了一个事件,就会在内部对这个函数做了一个回调,他拿到这个方法btnClick之后,就对其使用了btnClick.call(),并为其传递了一个参数undefined,也就是在执行这个函数的时候,给它绑定了一个动态的this,this指向了undefined。

    注意:在 React 中不能使用 return false 的方式阻止事件的默认行为,必须要显式的调用事件对象的 preventDefault 方法来阻止事件的默认行为。

    这里补充一下this的指向问题:在类中直接定义一个函数,并且将这个函数绑定到html原生的onClick事件上,当前这个函数的this默认情况下指向是undefined。因为在正常的DOM操作中,监听点击,监听函数中的this其实是节点对象(比如说是button对象)。

    因为React并不是直接渲染成真实的DOM,我们所编写的button只是一个语法糖,它的本质React的Element对象。那么在这里发生监听的时候,react给我们的函数绑定的this,默认情况下就是一个undefined;

    我们在绑定的函数中,可能想要使用当前对象,比如执行 this.setState 函数,就必须拿到当前对象的this,我们就需要在传入函数时,给这个函数直接绑定this,类似于下面的写法:

    1. <button onClick={this.changeText.bind(this)}>改变文本</button>

    我们希望绑定事件函数中的this指向这个组件对象,这里有三种解决方案:(1)显示绑定,就像上面所说的,使用bind方法将这个函数的this绑定到当前组件对象的this上

    1. <button onClick={this.btnClick.bind(this)}>按钮</button>

    也可以在构造方法中,将该方法进行重新赋值

    1. constructor(props) {
    2. super(props);
    3. this.state = { }
    4. this.btnClick = this.btnClick.bind(this);
    5. }

    这种方法固然是可以实现,但是可能会产生很多重复的代码。(2)使用箭头函数定义函数因为箭头函数永远不绑定this,它里面的this会读取上一层作用域的属性,如果没有找到就逐层向上查找。

    1. btnClick = () => { }

    (3)传入一个箭头函数,在箭头函数中调用需要执行的函数(推荐使用)

    1. <button onClick={() => { this.btnClick() }}>按钮</button>

    它的原理就是,当我们点击按钮时,就会执行箭头函数中的函数体,也就是执行事件绑定的方法。当执行这个方法的时候,由于箭头函数是不绑定this的,所以它会向上层作用域的render中进行查找,这样就会将this隐式绑定在事件函数上面。

    在执行事件函数时,有可能我们需要获取一些参数信息:比如event对象、其他参数 (1)情况一:获取event对象

    • 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
    • 假如我们用不到this,那么直接传入函数就可以获取到event对象;

    (2)情况二:获取更多参数

    • 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;

      1. <button onClick={ event => { this.btnClick(item, index, event) }}>按钮</button>

      9. React条件渲染

      某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:

    • 在vue中,我们会通过指令来控制:比如v-if、v-show;

    • 在React中,所有的条件判断都和普通的JavaScript代码一致;

    常见的条件渲染的方式有以下三种:

    • 方式一:条件判断语句,适合逻辑较多的情况
    • 方式二:三元运算符,适合逻辑比较简单
    • 方式三:与运算符&&,如果条件成立,渲染&&后面的组件;如果条件不成立,什么内容也不渲染; ```javascript render() {

      1. const { isLogin } = this.state;
      2. // 1.方案一:通过if判断: 逻辑代码非常多的情况
      3. let welcome = null;
      4. let btnText = null;
      5. if (isLogin) {
      6. welcome = <h2>欢迎回来~</h2>
      7. btnText = "退出";
      8. } else {
      9. welcome = <h2>请先登录~</h2>
      10. btnText = "登录";
      11. }
      12. return (
      13. <div>
      14. <div>我是div元素</div>
      15. {welcome}
      16. {/* 2.方案二: 三元运算符 */}
      17. <button onClick={e => this.loginClick()}>{isLogin ? "退出" : "登录"}</button>
    1. <h2>{isLogin ? "你好, React": null}</h2>
    2. {/* 3.方案三: 逻辑与&& */}
    3. {/* 逻辑与: 一个条件不成立, 后面的条件都不会进行判断了 */}
    4. <h2>{ isLogin && "你好, React" }</h2>
    5. { isLogin && <h2>你好, React</h2> }
    6. </div>
    7. )
    8. }
    1. 在Vue中,v-if是对元素隐藏时,会将元素送DOM树上删除掉;而v-show只是对元素进行隐藏,其原理就是给元素添加display:none,它更适用于频繁切换某个元素的显示/隐藏的情况,会大大的节省开销。
    2. 那在上面我们对React元素的显示和隐藏都相当于Vue中v-if,元素在隐藏之后就不会出现在DOM树上,我们可以用React来实现一下Vue中v-show指令的效果:
    3. ```javascript
    4. class App extends React.Component {
    5. constructor(props) {
    6. super(props);
    7. this.state = {
    8. isLogin: true
    9. }
    10. }
    11. render() {
    12. const { isLogin } = this.state;
    13. const titleDisplayValue = isLogin ? "block": "none";
    14. return (
    15. <div>
    16. <button onClick={e => this.loginClick()}>{isLogin ? "退出": "登录"}</button>
    17. <h2 style={{display: titleDisplayValue}}>你好, React</h2>
    18. </div>
    19. )
    20. }
    21. loginClick() {
    22. this.setState({
    23. isLogin: !this.state.isLogin
    24. })
    25. }
    26. }

    这样,元素隐藏之后还是会在DOM树上,只是不会显示在页面上,节省了开销,提高了页面的性能。

    10. React列表渲染

    在实际的开发过程中,我们通常会将请求的大量数据渲染成列表,在Vue中,我们可以使用v-for进行列表的渲染,而在React中需要我们通过JavaScript代码的方式组织数据,转成JSX。

    在React中,展示列表最多的方式就是使用数组的map方法,使用它来遍历数组元素,也可以在遍历的过程中对数组元素进行操作;除此之外,为我们还会用到filter方法过滤数据,使用slice方法截取数据等等。

    1. <ul>
    2. {
    3. this.state.numbers.filter(item => item >= 50).map(item => <li key={item.id}>{item}</li>)
    4. }
    5. </ul>

    需要注意的是,在渲染是,我们需要给渲染项添加一个key,不然会报错:warning:Each child in a list should have a unique “key” prop.在React 中,key 属性是给 React 自己用的一个特殊属性,就是说即使为一个组件设置 key 之后,我们也无法获取这个组件的 key 值。它是一种身份标识,每个 key 对应一个组件。key 的值必须保证唯一且稳定,它类似于数据库中的主键 id 一样,有且唯一。 key和React中的diff算法密切相关。

    11. React表单处理

    React的组件分为受控组件非受控组件:

    • 受控组件:由 React 来管理表单元素的值,同时表单元素的变化需要实时映射到 React 的 state 中,这个类似于双向数据绑定。不同的表单元素,React 控制方式是不一样的,如 input 用 value 来控制,checkbox 用 checked 来判断是否选中等。
    • 非受控组件:非受控组件,表单数据将交由 DOM 节点来处理。React 提供了一个 ref 属性,用来从 DOM 节点中获取表单数据。

    受控组件:常见的受控组件有文本框、单选框、复选框、下拉列表等。(1)文本框文本框包含类型为 text 、 number 的 input 元素和 textarea 元素。这些被受控的主要原理是通过元素的 value 属性设置表单元素的值,通过表单元素的 onChange 事件监听值的变化,并同步到 React 组件的 state 中。

    当用户输入框内容改变的时候, onChange 事件会被触发,进而相应的处理函数会把值的变化通过 setState 同步到组件的 state 中,同时由于 state 值的变化,又会重新渲染表单,进而实现对表单元素状态的控制。 (2)单选框在 HTML 中, 标签可以创建单选框,由标签上的 checked 属性决定是否选中,在React 中也是一样。(3)复选框在 HTML 中, 标签可以创建复选框,由标签上的 checked 属性决定是否选中,在 React中也是一样。(4)下拉列表在 HTML 中, 标签上使用 value 属性。

    注意:select 标签可以使用多选,只需在标签中加一个 multiple = {true} ,同时 React 状态变量使用数组,当然 onChange 处理函数也需按数组处理。

    1. class Register extends React.Component {
    2. constructor(props) {
    3. super(props)
    4. this.handleSubmit = this.handleSubmit.bind(this)
    5. this.state = {
    6. phone: '',
    7. username: '',
    8. password: ''
    9. }
    10. }
    11. handleChange = (e) => {
    12. this.setState({
    13. [e.target.name]: e.target.value
    14. })
    15. }
    16. handleSubmit(e) {
    17. const { phone, username, password} = this.state
    18. alert(`手机号:${phone} 昵称:${username} 密码:${password}`)
    19. e.preventDefault()
    20. }
    21. render() {
    22. const { phone, username, password} = this.state
    23. return (
    24. <div>
    25. <form>
    26. <div className="row">
    27. <span className="input-tip">手机号:</span>
    28. <input type="text" value={phone} name="phone" onChange={this.handleChange}/>
    29. </div>
    30. <div className="row">
    31. <span className="input-tip">昵 称:</span>
    32. <input type="text" value={username} name="username" onChange={this.handleChange}/>
    33. </div>
    34. <div className="row">
    35. <span className="input-tip">密 码:</span>
    36. <input type="password" value={password} name="password" onChange={this.handleChange}/>
    37. </div>
    38. </form>
    39. </div>
    40. )
    41. }
    42. }
    43. export default Register

    非受控组件:受控组件保证了表单元素的状态统一由 React 管理,但同时每个表单元素都需要通过 onChange 事件来绑定处理函数,然后将更改同步到 React 组件的 state 中,这个过程还是比较繁琐的。一种可替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。React 提供了一个 ref 属性,用来从 DOM 节点中获取表单数据。

    1. class Login extends React.Component {
    2. constructor(props) {
    3. super(props)
    4. this.handleSubmit = this.handleSubmit.bind(this)
    5. }
    6. handleSubmit(e) {
    7. alert(`昵称:${this.refs.username.value} 密码:${this.refs.password.value}`)
    8. e.preventDefault()
    9. }
    10. render() {
    11. return (
    12. <div>
    13. <form onSubmit={this.handleSubmit}>
    14. <div className={styles.row}>
    15. <input type="text" ref="username"/>
    16. </div>
    17. <div className={styles.row}>
    18. <input type="password" ref="password"/>
    19. </div>
    20. <div className={styles.row}>
    21. <input type="submit" value="登录"/>
    22. </div>
    23. </form>
    24. </div>
    25. )
    26. }
    27. }
    28. export default Login

    在填写完表单之后,我们通常会对表单进行校验,以确保表单数据格式准确。最常见的两种检验就是失去焦点校验提交时校验

    (1)失去焦点校验在失去焦点时加上校验规则,那么就需要监听onBlur事件,然后在onBlur事件中做验证规则处理。

    1. // 失去焦点校验
    2. class Register extends React.Component {
    3. // ...
    4. validator = {
    5. onBlur (e) {
    6. const rules = Rules[e.target.name]
    7. const value = e.target.value
    8. /* 失去焦点校验 */
    9. const verifyResult = verify(value, rules)
    10. if (verifyResult.error) {
    11. alert(verifyResult.message)
    12. }
    13. }
    14. }
    15. render () {
    16. <input {...this.validator} type="text" value={this.state.username} name="username" onChange={this.handleChange}/>
    17. <input {...this.validator} type="password" value={this.state.password} name="password" onChange={this.handleChange}/>
    18. }
    19. }
    20. // 校验规则
    21. export const Rules = {
    22. username: [
    23. { require: true, message: '昵称不能为空' },
    24. { min: 2, max: 20, message: '昵称长度不能小于2位,并且不能超过20位' },
    25. ],
    26. password: [
    27. { require: true, message: '密码不能为空' },
    28. { regular: /^\w*$/, message: '密码格式不正确,只能输入字母、数字、下划线' },
    29. { min: 6, max: 20, message: '密码长度不能小于6位,并且不能超过20位' },
    30. ]
    31. }
    32. // 处理校验规则
    33. export function verify (value, rules) {
    34. let verifyResult = { error: true, message: '填写必填项' }
    35. for (const rule of rules) {
    36. // 校验的几种方式:针对 require | regular | min | max 做了处理
    37. const errorVerify = (rule.require && value === '') ||
    38. (rule.regular && !rule.regular.test(value)) ||
    39. (rule.min && value.length < rule.min) ||
    40. (rule.max && value.length > rule.max)
    41. // 校验错误,则返回error及message,并跳出循环,不继续执行循环
    42. if (errorVerify) {
    43. verifyResult = { error: true, message: rule.message }
    44. break
    45. }
    46. // 校验成功,将 error 设置为 false
    47. verifyResult = { error: false, message: '' }
    48. }
    49. return verifyResult
    50. }

    这里我们定义了 validator 对象,对象中有一个 onBlur() 函数, onBlur() 函数用来处理失去焦点的验证。在 input 中,加入了 {...this.validator} ,这样写是为了以后的扩展性(比如可以在 validator 中加入其他的处理函数),当然也等同于 onBlur={onBlur}

    在表单校验的主逻辑中,只获取了校验结果和提示内容,至于如何验证都交给了 verify 函数去处理,这样我们主逻辑代码就很清晰了。这里我们将校验规则与校验规则的处理放在了单独的文件中,这样主逻辑代码看起来更加简洁,在主逻辑代码中,我们只管使用相应的校验规则,而不用关心校验规则是如何进行的。

    (2)提交时校验如果只对单个输入框失去焦点时进行校验,并不能保证提交时都是正确的数据,所以还需要在点击提交时,再次校验规则。

    1. // 提交时校验
    2. class Register extends React.Component {
    3. // ...
    4. handleSubmit(e) {
    5. e.preventDefault()
    6. /* 提交校验 */
    7. const verifyResult = submitVerify(this.state)
    8. if (verifyResult.error) {
    9. alert(verifyResult.message)
    10. return
    11. }
    12. alert(`昵称:${this.state.username} 密码:${this.state.password}`)
    13. }
    14. // ...
    15. }
    16. // 校验规则
    17. export const Rules = {
    18. username: [
    19. { require: true, message: '昵称不能为空' },
    20. { min: 2, max: 20, message: '昵称长度不能小于2位,并且不能超过20位' },
    21. ],
    22. password: [
    23. { require: true, message: '密码不能为空' },
    24. { regular: /^\w*$/, message: '密码格式不正确,只能输入字母、数字、下划线' },
    25. { min: 6, max: 20, message: '密码长度不能小于6位,并且不能超过20位' },
    26. ]
    27. }
    28. // 处理校验规则
    29. export function submitVerify (state) {
    30. let verifyResult = {}
    31. /* 从Rules中取key值 */
    32. const keyArray = Object.keys(Rules)
    33. /* 这里通过循环对 Rules 中的每个值进行了遍历,
    34. * 并在每次遍历的过程中,都使用 verify() 函数进行验证处理 */
    35. for (const key of keyArray) {
    36. const rules = Rules[key]
    37. /* 单次处理和我们在失去焦点时处理方式一样 */
    38. verifyResult = verify(state[key], rules)
    39. /* 如果发现填写的有错误项,直接退出循环 */
    40. if (verifyResult.error) break
    41. }
    42. return verifyResult
    43. }

    六、JSX是如何变成DOM的?

    React官网中对JSX的定义:

    JSX 是 JavaScript 的一种语法扩展,它和模板语言很接近,但是它充分具备 JavaScript 的能力。

    那关键问题就是JSX是如何在JavaScript中生效的,官方的解释:

    JSX 会被编译为 React.createElement(), React.createElement() 将返回一个叫作“React Element”的 JS 对象。

    实际上,JSX仅仅只是 React.createElement(component, props, …children) 函数的语法糖。所有的jsx最终都会被转换成React.createElement的函数调用。这也就意味着,我们写的JSX其实写的就是 React.createElement,虽然它看起来很像HTML。这也印证了JSX充分具备JavaScript的能力。React内部处理createElement的源码如下:**

    1. export function createElement(type, config, children) {
    2. // propName 变量用于储存后面需要用到的元素属性
    3. let propName;
    4. // props 变量用于储存元素属性的键值对集合
    5. const props = {};
    6. // key、ref、self、source 均为 React 元素的属性
    7. let key = null;
    8. let ref = null;
    9. let self = null;
    10. let source = null;
    11. // config 对象中存储的是元素的属性
    12. if (config != null) {
    13. // 依次对 ref、key、self 和 source 属性赋值
    14. if (hasValidRef(config)) {
    15. ref = config.ref;
    16. }
    17. // 此处将 key 值字符串化
    18. if (hasValidKey(config)) {
    19. key = '' + config.key;
    20. }
    21. self = config.__self === undefined ? null : config.__self;
    22. source = config.__source === undefined ? null : config.__source;
    23. // 把 config 里面的属性都一个一个挪到 props 这个之前声明好的对象里面
    24. for (propName in config) {
    25. if (
    26. // 筛选出可以提进 props 对象里的属性
    27. hasOwnProperty.call(config, propName) &&
    28. !RESERVED_PROPS.hasOwnProperty(propName)
    29. ) {
    30. props[propName] = config[propName];
    31. }
    32. }
    33. }
    34. // childrenLength 指的是当前元素的子元素的个数,减去的 2 是 type 和 config 两个参数占用的长度
    35. const childrenLength = arguments.length - 2;
    36. // 如果抛去type和config,就只剩下一个参数,一般意味着文本节点出现了
    37. if (childrenLength === 1) {
    38. // 直接把这个参数的值赋给props.children
    39. props.children = children;
    40. // 处理嵌套多个子元素的情况
    41. } else if (childrenLength > 1) {
    42. // 声明一个子元素数组
    43. const childArray = Array(childrenLength);
    44. // 把子元素推进数组里
    45. for (let i = 0; i < childrenLength; i++) {
    46. childArray[i] = arguments[i + 2];
    47. }
    48. // 最后把这个数组赋值给props.children
    49. props.children = childArray;
    50. }
    51. // 处理 defaultProps
    52. if (type && type.defaultProps) {
    53. const defaultProps = type.defaultProps;
    54. for (propName in defaultProps) {
    55. if (props[propName] === undefined) {
    56. props[propName] = defaultProps[propName];
    57. }
    58. }
    59. }
    60. // 最后返回一个调用ReactElement执行方法,并传入刚才处理过的参数
    61. return ReactElement(
    62. type,
    63. key,
    64. ref,
    65. self,
    66. source,
    67. ReactCurrentOwner.current,
    68. props,
    69. );
    70. }

    入参:createElement需要传递三个参数:(1)参数一:type,表示当前ReactElement的类型;

    • 如果是标签元素,那么就使用字符串表示 “div”;
    • 如果是组件元素,那么就直接使用组件的名称;

    (2)参数二:config,以对象形式传入,组件所有的属性都会以键值对的形式存储在 config 对象中;(3)参数三:children,存放在标签中的内容,以children数组的方式进行存储;

    1. // jsx -> babel -> React.createElement()
    2. const message1 = <h2>Hello React</h2>;
    3. const message2 = React.createElement("h2", null, "Hello React");

    createElement的处理流程:未命名文件 (1).png简单来说,React.createElement就是一个“转换器”,将用户输入的参数,进行一定的格式化,最终通过调用ReactElement来实现元素的创建。

    出参:createElement 执行到最后会 return 一个针对 ReactElement 的调用。下面是ReactElement的源码:

    1. const ReactElement = function(type, key, ref, self, source, owner, props) {
    2. const element = {
    3. // REACT_ELEMENT_TYPE是一个常量,用来标识该对象是一个ReactElement
    4. $$typeof: REACT_ELEMENT_TYPE,
    5. // 内置属性赋值
    6. type: type,
    7. key: key,
    8. ref: ref,
    9. props: props,
    10. // 记录创造该元素的组件
    11. _owner: owner,
    12. };
    13. //
    14. if (__DEV__) {
    15. //__DEV__ 环境下的处理
    16. }
    17. return element;
    18. };

    根据上面的源码可以看出,ReactElement实际上就做了一件事,将传入的参数进行组装,将它们组装进element对象中,并将它返回给了React.createElement,最终 React.createElement 又把它交回到了开发者手中。

    整体的流程图如下:未命名文件 (3).png通过 React.createElement 最终创建出来一个ReactElement对象。

    React利用ReactElement对象组成了一个JavaScript的对象树,对象树就是虚拟DOM(Virtual DOM)。下面是一个ReactElement对象的示例代码:

    1. render() {
    2. // jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 真实DOM
    3. // jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 原生的控件(UIButton/Button)
    4. var elementObj = (
    5. <div>
    6. <div className="header">
    7. <h1 title="标题">标题</h1>
    8. </div>
    9. <div className="content">
    10. <h2>内容</h2>
    11. <button>按钮</button>
    12. <button>+1</button>
    13. <a href="http://www.baidu.com">百度一下</a>
    14. </div>
    15. <div className="footer">
    16. <p>底部</p>
    17. </div>
    18. </div>
    19. )
    20. console.log(elementObj);
    21. return elementObj;
    22. }

    image.png上面就是生成的对象树,也就是虚拟DOM,那虚拟DOM是如何转化为真是的DOM结构的呢?它靠的就是render函数。在每一个 React 项目的入口文件中,都少不了对 React.render 函数的调用。

    ReactDOM.render 方法的入参规则:

    1. ReactDOM.render(
    2. // 需要渲染的元素(ReactElement)
    3. element,
    4. // 元素挂载的目标容器(一个真实DOM)
    5. container,
    6. // 回调函数,可选参数,可以用来处理渲染结束后的逻辑
    7. [callback]
    8. )

    ReactDOM.render 方法可以接收 3 个参数,其中第二个参数就是一个真实的 DOM 节点这个真实的 DOM 节点充当“容器”的角色,React 元素最终会被渲染到这个“容器”里面去。上面所说的 App 组件,它对应的 render 调用是这样的:

    1. ReactDOM.render(<App />, document.getElementById("app"));

    需要注意的是,这个真实的DOM一定是确实存在的,比如在 App 组件对应的 index.html 中,已经提前预置 了 id 为 root 的根节点:

    1. <body>
    2. <div id="root"></div>
    3. </body>

    那为什么要操作虚拟DOM,而不是操作真实的DOM呢?原因如下:

    • 很难跟踪状态发生的改变:原有的开发模式,我们很难跟踪到状态发生的改变,不方便针对我们应用程序进行调试;
    • 操作真实DOM性能较低:传统的开发模式会进行频繁的DOM操作,而这一的做法性能非常的低;

    DOM操作性能非常低:**

    • 首先,document.createElement本身创建出来的就是一个非常复杂的对象;
    • 其次,DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作;

    虚拟DOM可以帮助我们从命令式编程转到了声明式编程的模式。下面是React官方的说法:Virtual DOM 是一种编程理念。

    • 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
    • 我们可以通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation);

    这种编程的方式赋予了React声明式的API:

    • 只需要告诉React希望让UI是什么状态;
    • React来确保DOM和这些状态是匹配的;
    • 不需要直接进行DOM操作,只可以从手动更改DOM、属性操作、事件处理中解放出来;

      七、案例练习

      下面就来做一个简单的案例,其要求如下:

    • 在界面上以表格的形式,显示一些书籍的数据

    • 在底部显示书籍的总价格
    • 点击+或者点击-可以增加或减少书籍数量(如果为1,那么不能继续-)
    • 点击移除按钮,可以将书籍移除(当所有书籍移除完毕之后,显示:购物车为空~)

    页面效果如下:image.png代码实现:

    1. <style>
    2. table {
    3. border: 1px solid #eee;
    4. border-collapse: collapse;
    5. }
    6. th, td {
    7. border: 1px solid #eee;
    8. padding: 10px 16px;
    9. text-align: center;
    10. }
    11. th {
    12. background-color: #ccc;
    13. }
    14. .count {
    15. margin: 0 5px;
    16. }
    17. </style>
    1. class App extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. this.state = {
    5. books: [
    6. {
    7. id: 1,
    8. name: '《算法导论》',
    9. date: '2006-9',
    10. price: 85.00,
    11. count: 2
    12. },
    13. {
    14. id: 2,
    15. name: '《UNIX编程艺术》',
    16. date: '2006-2',
    17. price: 59.00,
    18. count: 1
    19. },
    20. {
    21. id: 3,
    22. name: '《编程珠玑》',
    23. date: '2008-10',
    24. price: 39.00,
    25. count: 1
    26. },
    27. {
    28. id: 4,
    29. name: '《代码大全》',
    30. date: '2006-3',
    31. price: 128.00,
    32. count: 1
    33. }
    34. ]
    35. }
    36. }
    37. renderBooks() {
    38. return (
    39. <div>
    40. <table>
    41. <thead>
    42. <tr>
    43. <th></th>
    44. <th>书籍名称</th>
    45. <th>出版日期</th>
    46. <th>价格</th>
    47. <th>购买数量</th>
    48. <th>操作</th>
    49. </tr>
    50. </thead>
    51. <tbody>
    52. {
    53. this.state.books.map((item, index) => {
    54. return (
    55. <tr>
    56. <td>{index+1}</td>
    57. <td>{item.name}</td>
    58. <td>{item.date}</td>
    59. <td>{this.formatPrice(item.price)}</td>
    60. <td>
    61. <button disabled={item.count <= 1} onClick={e => this.changeBookCount(index, -1)}>-</button>
    62. <span className="count">{item.count}</span>
    63. <button onClick={e => this.changeBookCount(index, 1)}>+</button>
    64. </td>
    65. <td><button onClick={e => this.removeBook(index)}>移除</button></td>
    66. </tr>
    67. )
    68. })
    69. }
    70. </tbody>
    71. </table>
    72. <h2>总价格: {this.getTotalPrice()}</h2>
    73. </div>
    74. )
    75. }
    76. // 购物车为空
    77. renderEmptyTip() {
    78. return <h2>购物车为空~</h2>
    79. }
    80. // 条件渲染
    81. render() {
    82. return this.state.books.length ? this.renderBooks(): this.renderEmptyTip();
    83. }
    84. // 修改书籍数量
    85. changeBookCount(index, count) {
    86. const newBooks = [...this.state.books];
    87. newBooks[index].count += count;
    88. this.setState({
    89. books: newBooks
    90. })
    91. }
    92. // 移除书籍
    93. removeBook(index) {
    94. this.setState({
    95. books: this.state.books.filter((item, indey) => index != indey)
    96. })
    97. }
    98. // 计算书籍总价格
    99. getTotalPrice() {
    100. const totalPrice = this.state.books.reduce((preValue, item) => {
    101. return preValue + item.count * item.price;
    102. }, 0);
    103. return this.formatPrice(totalPrice);
    104. }
    105. // 处理书籍价格格式
    106. formatPrice(price) {
    107. if (typeof price !== "number") {
    108. price = Number("aaa") || 0;
    109. }
    110. return "¥" + price.toFixed(2);
    111. }
    112. }
    113. ReactDOM.render(<App/>, document.getElementById("app"));

    通常我们会将渲染相关的函数放在render函数的上方,将功能性的函数放在render函数的下方。

    需要注意, React中设计原则中有一条: state中的数据的不可变性,所以我们不能直接操作数组中元素,需要对其进行拷贝在进行操作。

    以太坊cppgolang区别 编程

    以太坊cppgolang区别

    以太坊是一种去中心化的开源平台,它采用智能合约技术,旨在构建和运行不受干扰的分布式应用程序。作为目前最受欢迎的区块链平台之一,以太坊提供了多种编程语言的支持,其
    progolang 编程

    progolang

    Go语言(Golang)是由Google开发的一门静态类型编程语言。作为一名专业的Golang开发者,我深知这门语言的优势和特点。在本文中,我将介绍Golang
    golangn个发送者 编程

    golangn个发送者

    Golang是一种开源的编程语言,由Google团队开发,旨在提高程序的并发性和简化软件开发过程。在Go语言中,有时需要向多个接收者发送信息。本文将介绍如何在G
    golang技能图谱 编程

    golang技能图谱

    从互联网行业的快速发展到人工智能技术的日益成熟,各种编程语言也应运而生。而在这众多的编程语言中,Golang(即Go)作为一门强大且高效的开发语言备受关注。Go
    评论:0   参与:  6