S13-01 React-基础
[TOC]
API
- ReactDOM. createRoot(domNode):
参数:domNode,返回:root,用于创建一个 React 根,之后渲染的内容会包含在这个根中- 参数
- domNode:``,将渲染的内容,挂载到哪一个 HTML 元素上
- 返回
- root:
Root对象,通过该对象,React 将会管理该节点内的 DOM 元素
- root.render(jsx):
参数:jsx,返回:,渲染函数- 参数
- jsx:``,要渲染的根组件或 DOM 元素
- React.createElement(type, props, ...children?):
参数:,返回:ReactElement,用于创建一个 React 元素并返回该元素的对象- 参数
- type:
string,元素类型,可以是 HTML 标签名或自定义组件。 - props:
object,属性对象,包括该元素的所有属性和事件处理函数等。 - ...children?:``,可选的子元素节点。可以为文本节点、React 元素等。
- 返回
- ReactElement:``,虚拟 DOM 对象
React 在前端的地位
React 是什么呢?
相信每个做开发的人对它都或多或少有一些印象;
这里我们来看一下官方对它的解释:用于构建用户界面的 JavaScript 库;

目前对于前端开发来说,几乎很少直接使用原生的 JavaScript 来开发应用程序,而是选择一个 JavaScript 库(框架)。
在过去的很长时间内,jQuery是被使用最多的 JavaScript 库;
在过去的一份调查中显示,全球前 10,000 个访问最高的网站中,有 65%使用了 jQuery,是当时最受欢迎的 JavaScript 库;
但是,目前甚至已经处于淘汰的边缘了;
而无论是国内外,最流行的其实是三大框架:Vue、React、Angular。
目前 React 在前端处于什么地位?
目前前端最流行的是三大框架:Vue、React、Angular。

框架数据对比(Google 指数)

npm 下载量

框架数据对比(GitHub)

HackerRank 调查显示
在 HackerRank 中,有一份调用,你更想要学习的 framework(框架):

招聘岗位对 React 的要求

React 的技术特点
React 由 Facebook 来更新和维护,它是大量优秀程序员的思想结晶:
React 的流行不仅仅局限于普通开发工程师对它的认可;
大量流行的其他框架借鉴 React 的思想;
Vue.js 框架设计之初,有很多的灵感来自 Angular 和 React。
包括Vue3很多新的特性,也是借鉴和学习了 React;
比如React Hooks是开创性的新功能(也是我们课程的重点);
Vue Composition API学习了 React Hooks 的思想;
Flutter 的很多灵感都来自 React,来自官网的一段话:(SwiftUI 呢)
事实上Flutter中的 Widget – Element – RenderObject;
对应 React 的就是 JSX – 虚拟 DOM – 真实 DOM;
所以 React 可以说是前端的先驱者,它总是会引领整个前端的潮流。
Vue 和 React 的选择
首先,React 和 Vue 是前端开发人员必须掌握的两个框架。

下面的观点是一个目前比较普遍的共识,没有贬低任何框架的意思。
大中型公司选择 React 会较多,灵活和稳定;
中小型公司选择 Vue 会较多,易上手和代码统一;
难度:React 难度大于 Vue
工资:React 工资大于 Vue
如何学习 React?

React 课程体系

React 项目实战-弘源头条(候补)

React 项目实战-弘源彼迎(暂定)

哪些人适合学习?
React 和 Vue 都是前端工程师必须掌握的两个框架:
大多数同学都是学习了 Vue,并且刚开始工作都是使用的 Vue,所以通常对 Vue 是有深入的感情的(某些同学可能是小程序);
但是在前端整个职业发展的过程中,不能仅仅将自己局限在某一个框架或者技术中;
并且 React 是作为前端进阶来说自己必须要掌握的一个框架;
本次课程要求:
本课程要求掌握前端的核心开发语言:HTML、CSS、JavaScript。
React 本身是 JavaScript 的要求相对会更高一些,所以也需要掌握一些高级的 JavaScript 语法,比如 ES6 以上的语法、this 绑定规则等等;
整个课程从零讲解 React,所以并不要求之前学习过 React 相关的知识。
如果你之前已经掌握了一些 React,也可以从课程中学习到非常多其他的核心知识和实战细节,也包括原理、源码、架构等知识内容。
所以无论你目前处于前端哪一个阶段,都可以在这个过程中有很多的收获。
React 的介绍(技术角度)
React 是什么?
React:用于构建用户界面的 JavaScript 库;
React 官网文档:https://zh-hans.reactjs.org/

React 的特点-声明式编程
声明式编程:
声明式编程是目前整个大前端开发的模式:Vue、React、Flutter、SwiftUI;
它允许我们只需要维护自己的状态,当状态改变时,React 可以根据最新的状态去渲染我们的 UI 界面;

React 特点-组件化开发
组件化开发:
组件化开发页面目前前端的流行趋势,我们会将复杂的界面拆分成一个个小的组件;
如何合理的进行组件的划分和设计也是后面我会讲到的一个重点;

React 的特点-多平台适配
多平台适配:
2013 年,React 发布之初主要是开发 Web 页面;
2015 年,Facebook 推出了ReactNative,用于开发移动端跨平台;(虽然目前 Flutter 非常火爆,但是还是有很多公司在使用 ReactNative);
2017 年,Facebook 推出ReactVR,用于开发虚拟现实 Web 应用程序;(VR 也会是一个火爆的应用场景);

Hello React
为了演练 React,我们可以提出一个小的需求:
在界面显示一个文本:Hello World
点击下方的一个按钮,点击后文本改变为 Hello React
当然,你也可以使用 jQuery 和 Vue 来实现,甚至是原生方式来实现,对它们分别进行对比学习
<div id="root"></div>
<script crossorigin src="../lib/react.js"></script>
<script crossorigin src="../lib/react-dom.js"></script>
<script src="../lib/babel.js"></script>
<script type="text/babel">
// 属性
let msg = "Hello World";
// 方法
function onChange() {
msg = "Hello React"
+ rootRender()
}
// 渲染
+ const root = ReactDOM.createRoot(document.querySelector("#root"));
+ rootRender()
+ function rootRender() {
+ root.render(
+ <div>
+ <h2>{msg}</h2>
+ <button onClick={onChange}>修改文本</button>
+ </div>
+ );
+ }
</script>
React 的开发依赖
开发 React 必 3 须依赖三个库
react:包含 react 所必须的核心代码
react-dom:react 渲染在不同平台所需要的核心代码
babel:将 jsx 转换成普通 js 代码的工具
第一次接触 React 会被它繁琐的依赖搞蒙,居然依赖这么多东西: (直接放弃?)
对于 Vue 来说,我们只是依赖一个 vue.js 文件即可,但是 react 居然要依赖三个包。
其实呢,这三个库是各司其职的,目的就是让每一个库只单纯做自己的事情;
在 React 的 0.14 版本之前是没有 react-dom 这个概念的,所有功能都包含在 react 里;
为什么要进行拆分呢?原因就是 react-native。
react 包中包含了 react web 和 react-native 所共同拥有的核心代码。
react-dom针对 web 和 native 所完成的事情不同:
- web 端:react-dom 会将 jsx 最终渲染成真实的DOM,显示在浏览器中
- native 端:react-dom 会将 jsx 最终渲染成原生的控件(比如 Android 中的 Button,iOS 中的 UIButton)。
Babel 和 React 的关系
babel 是什么呢?
Babel ,又名 Babel.js。
是目前前端使用非常广泛的编译器、转移器。
比如当下很多浏览器并不支持 ES6 的语法,但是确实 ES6 的语法非常的简洁和方便,我们开发时希望使用它。
那么编写源码时我们就可以使用 ES6 来编写,之后通过 Babel 工具,将 ES6 语法转成大多数浏览器都支持的 ES5 的语法。
React 和 Babel 的关系:
默认情况下开发 React 其实可以不使用 babel。
但是前提是我们自己使用 React.createElement 来编写源代码,它编写的代码非常的繁琐和可读性差。
那么我们就可以直接编写 jsx(JavaScript XML)的语法,并且让 babel 帮助我们转换成 React.createElement。
后续还会详细讲到;
React 的依赖引入
所以,我们在编写 React 代码时,这三个依赖都是必不可少的。
那么,如何添加这三个依赖:
方式一:直接 CDN 引入
方式二:下载后,添加本地依赖
方式三:通过 npm 管理(后续脚手架再使用)
1、直接 CDN 引入
暂时我们直接通过 CDN 引入,来演练下面的示例程序:
- 这里有一个crossorigin的属性,这个属性的目的是为了拿到跨域脚本的错误信息
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>建议你验证使用的 CDN 是否设置了
Access-Control-Allow-Origin: *HTTP 请求头:
2、本地引入
下载 3 个依赖包到本地

本地引入
<div id="root"></div>
+
<script crossorigin src="../lib/react.js"></script>
+
<script crossorigin src="../lib/react-dom.js"></script>
+
<script src="../lib/babel.js"></script>
+
<script type="text/babel">
...
</script>Hello World
第一步:在界面上通过 React 显示一个 Hello World
注意:这里我们编写 React 的 script 代码中,必须添加 type="text/babel",作用是可以让 babel 解析 jsx 的语法

ReactDOM. createRoot函数:用于创建一个 React 根,之后渲染的内容会包含在这个根中
- 参数:将渲染的内容,挂载到哪一个 HTML 元素上
- 这里我们已经提定义一个 id 为 app 的 div
**root.render函数:**渲染元素
- 参数:要渲染的根组件
我们可以通过{}语法来引入外部的变量或者表达式

Hello React-错误做法

Hello React-正确做法

Hello React-组件化开发
整个逻辑其实可以看做一个整体,那么我们就可以将其封装成一个组件:
我们说过root.render 参数是一个HTML 元素或者一个组件;
所以我们可以先将之前的业务逻辑封装到一个组件中,然后传入到 ReactDOM.render 函数中的第一个参数;
**在 React 中,如何封装一个组件呢?**这里我们暂时使用类的方式封装组件:
1.定义一个类(类名大写,组件的名称是必须大写的,小写会被认为是 HTML 元素),继承自React.Component
2.实现当前组件的 render 函数
- render 当中返回的 jsx 内容,就是之后 React 会帮助我们渲染的内容
// 组件 - App
+ class App extends React.Component {
// 属性
+ constructor() {
+ super()
+ this.state = {
+ msg: 'Hello World'
+ }
+ }
// 方法
onChange() {
+ this.setState({
msg: 'Hello React'
})
}
// 渲染
+ render() {
return (
<div>
<div>{this.state.msg}</div>
// 注意2:在绑定事件函数时,需要显式bind(this),事件函数内部才能获取到当前组件实例App
+ <button onClick={this.onChange.bind(this)}>修改文本</button>
</div>
)
}
}
// 渲染
const root = ReactDOM.createRoot(document.querySelector('#root'))
// 注意1:此处render参数中不能用引号包裹<App/>
+ root.render(<App/>)注意:
- 1、render 参数中不能用引号包裹
<App/> - 2、在绑定事件函数时,需要显式 bind(this),事件函数内部才能获取到当前组件实例 App
组件化-数据依赖
组件化问题一:数据在哪里定义?
在组件中的数据,我们可以分成两类:
参与界面更新的数据:当数据变量时,需要更新组件渲染的内容;
不参与界面更新的数据:当数据变量时,不需要更新将组建渲染的内容;
参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象的 state 中
我们可以通过在构造函数中
this.state = {定义的数据}当我们的数据发生变化时,我们可以调用 `this.setState 来更新数据,并且通知 React 进行 update 操作;
- 在进行 update 操作时,就会重新调用 render 函数,并且使用最新的数据,来渲染界面
// 组件 - App
class App extends React.Component {
// 属性
constructor() {
super()
+ this.state = {
+ msg: 'Hello World'
+ }
}
// 渲染
render() {
return (
<div>
+ <div>{this.state.msg}</div>
</div>
)
}
}注意:
- 1、设置属性:在构造函数中设置:
this.state = { key: value } - 2、访问属性:在渲染函数中访问:
<div>{ this.state.msg }</div> - 3、修改属性:在事件函数中修改:
this.setState({ key: newValue })
组件化-事件绑定
组件化问题二:事件绑定中的 this
- 在类中直接定义一个函数,并且将这个函数绑定到元素的 onClick 事件上,当前这个函数的this 指向的是谁呢?
默认情况下是 undefined
很奇怪,居然是undefined;
因为在正常的 DOM 操作中,监听点击,监听函数中的 this 其实是节点对象(比如说是 button 对象);
这是因为 React 并不是直接渲染成真实的 DOM,我们所编写的 button 只是一个语法糖,它的本质是 React 的 Element 对象;
那么在这里发生监听的时候,react 在执行函数时并没有绑定 this,默认情况下就是一个 undefined;
我们在绑定的函数中,可能想要使用当前对象,比如执行 this.setState 函数,就必须拿到当前对象的 this
我们就需要在传入函数时,给这个函数直接绑定 this
类似于下面的写法:<button onClick={this.changeText.bind(this)}>改变文本</button>
// 方法
onChange() {
+ this.setState({
msg: 'Hello React'
})
}
// 渲染
+ render() {
return (
<div>
<div>{this.state.msg}</div>
// 注意2:在绑定事件函数时,需要显式bind(this),事件函数内部才能获取到当前组件实例App
+ <button onClick={this.onChange.bind(this)}>修改文本</button>
</div>
)
}setState()方法做的事情:
- 1、修改 state 中的值
- 2、自动重新执行 render 函数
绑定事件函数的方法:
onClick={this.btnClick}onClick={this.btnClick.bind(this)}onClick={() => this.btnClick()
案例:电影列表展示
方法一:将 movies 数据转成 liEls 数组

方法二:通过 map 映射 movies 数据
// 组件-App
class App extends React.Component {
// 属性
constructor() {
super()
this.state = {
+ movies: ['阿凡达', '流浪地球', '外星人ET', '变形金刚', '速度与激情', '致青春']
}
}
// 渲染
render() {
return (
<div>
<h3>电影列表</h3>
<ul>
+ { this.state.movies.map(item => <li key={item}>{item}</li>) }
</ul>
</div>
)
}
}
// 渲染
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
案例:计数器
// 组件
class App extends React.Component {
// 属性
constructor() {
super()
this.state = {
count: 100
}
}
// 渲染
render() {
+ const { count } = this.state
return (
<div>
<div className="count">当前计数:{ count }</div>
+ <button onClick={this.increment.bind(this)}> +1 </button>
+ <button onClick={this.decrement.bind(this)}> -1 </button>
</div>
)
}
// 方法
+ increment() {
+ this.setState({ count: this.state.count + 1 })
+ }
+ decrement() {
+ this.setState({ count: this.state.count - 1 })
+ }
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
VSCode 代码片段
我们在前面练习 React 的过程中,有些代码片段是需要经常写的,我们在 VSCode 中我们可以生成一个代码片段,方便我们快速生成。
VSCode 中的代码片段有固定的格式,所以我们一般会借助于一个在线工具来完成。
具体的步骤如下:
第一步,复制自己需要生成代码片段的代码
第二步,https://snippet-generator.app/在该网站中生成代码片段
第三步,在 VSCode 中配置代码片段
模板:
<!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></title>
</head>
<body>
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
// 组件
class App extends React.Component {
// 属性
constructor() {
super()
this.state = {}
}
// 渲染
render() {
return <div></div>
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App />)
</script>
</body>
</html>代码片段过程

jsx 语法
认识 jsx
<script type="text/babel">
// 1. 定义根组件 const el = <div>Hello World</div>
// 2. 渲染根组件 const root = ReactDOM.createRoot(document.querySelector('#root')) root.render(el)
</script>这段 element 变量的声明右侧赋值的标签语法<div>Hello World</div>是什么呢?
它不是一段字符串(因为没有使用引号包裹);
它看起来是一段 HTML 元素,但是我们能在 js 中直接给一个变量赋值 html 吗?
其实是不可以的,如果我们将 type="text/babel" 去除掉,那么就会出现语法错误;
它到底是什么呢?其实它是一段 jsx 的语法;
JSX 是什么?
JSX 是 JS 语法扩展,可以让你在 JS 文件中书写类似 HTML 的标签。
jsx 是一种 JavaScript 的语法扩展(JavaScript eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段 XML 语法;
它用于描述我们的 UI 界面,并且其完全可以和 JavaScript 融合在一起使用;
它不同于 Vue 中的模块语法,你不需要专门学习模块语法中的一些指令(比如 v-for、v-if、v-else、v-bind);

为什么 React 选择了 jsx
React 认为渲染逻辑本质上与其他 UI 逻辑存在内在耦合
比如 UI 需要绑定事件(button、a 原生等等);
比如 UI 中需要展示数据状态;
比如在某些状态发生改变时,又需要改变 UI;
他们之间是密不可分,所以 React 没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component);
- 当然,后面我们还是会继续学习更多组件相关的东西;
在这里,我们只需要知道,jsx 其实是嵌入到 JavaScript 中的一种结构语法;
jsx 的书写规范
1、jsx 的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个 div 元素(或者使用后面我们学习的Fragment);
2、为了方便阅读,我们通常在 jsx 的外层包裹一个小括号(),这样可以方便阅读,并且 jsx 可以进行换行书写;
3、jsx 中的标签可以是单标签,也可以是双标签;如果是单标签,必须以*/>*结尾;
jsx 基本使用
jsx 中的注释
<div>
+ {/* 这是注释 */}
<div>{msg}</div>
</div>注意: 在.jsx文件中可以直接通过快捷键ctrl + / 添加 jsx 的注释
jsx 嵌入变量作为子元素
情况一:当变量是Number、String、Array类型时,可以直接显示
js// 属性 constructor() { super() this.state = { + msg: 'Hello React', + num: 100, + arr: ['历史', '语文', '数学'], } } // 渲染 render() { const { msg, num, arr } = this.state return ( <div> {/* 1. 直接显示String, Number, Array类型的数据 */} + <div>{ msg }</div> + <div>{ num }</div> + <div>{ arr }</div> }
情况二:当变量是null、undefined、Boolean类型时,内容为空;
js// 属性 constructor() { super() this.state = { + n: null, + u: undefined, + b: true, } } // 渲染 render() { const { n, u, b } = this.state return ( <div> {/* 2. 空白显示null, undefined, Boolean类型的数据 */} + <div>{ n }</div> + <div>{ u }</div> + <div>{ b }</div> }如果希望可以显示 null、undefined、Boolean,那么需要转成字符串;
转换的方式有很多,比如 toString 方法、和空字符串拼接,String(变量)等方式;

情况三:Object对象类型不能作为子元素(not valid as a React child)
js// 属性 constructor() { super() this.state = { + obj: { name: 'Jack' } } } // 渲染 render() { const { obj } = this.state return ( <div> {/* 3. 报错:Object类型的数据 */} + <div>{ obj }</div> }
jsx 嵌入表达式
运算表达式
三元运算符
执行一个函数
运算表达式
// 属性
constructor() {
super()
this.state = {
firstName: '张',
lastName: '飞',
num1: 10,
num2: 30
}
}
// 渲染
render() {
const { firstName, lastName, num1, num2 } = this.state
return (
<div>
{/* 1. 算数表达式 */}
+ <div>{ firstName + lastName }</div>
+ <div>{ num1 + num2 }</div>
+ <div>{ num1 - num2 }</div>
+ <div>{ num1 * num2 }</div>
+ <div>{ num1 / num2 }</div>
+ <div>{ num1 % num2 }</div>
+ <div>{ Math.abs(num1 - num2) }</div>
)
}三元运算符
// 属性
constructor() {
super()
this.state = {
+ flag: true
}
}
// 渲染
render() {
const { flag } = this.state
return (
<div>
{/* 2. 三元运算符 */}
+ <div>{ flag ? '真' : '假' }</div>
)
}执行一个函数
// 渲染
render() {
return (
{/* 3. 执行函数 */}
+ <div>{ this.getRadom() }</div>
)
}
+ getRadom() {
return Math.floor(Math.random() * 10)
}属性绑定
比如元素都会有 title 属性
比如 img 元素会有 src 属性
比如 a 元素会有 href 属性
比如元素可能需要绑定 class
比如原生使用内联样式 style
基本绑定
基本属性:title、src、href 等
this.state = {
msg: 'Hello React',
title: 'React',
href: 'https://www.baidu.com',
src: 'https://n.sinaimg.cn/sinacn10211/360/w180h180/20191010/8404-ifrwayx4734762.jpg',
}
render() {
return (
<div>
{/* 注意:绑定基本属性 */}
+ <div title={title}>{msg}</div>
+ <a href={href}>百度一下</a>
+ <img src={src} alt={title}/>
)
}绑定 class
写法一:字符串拼接
// 属性
constructor() {
super()
this.state = {
+ isActive: true
}
}
// 渲染
render() {
+ const { isActive } = this.state
return (
<div>
{/* 1. 字符串拼接 */}
+ <h2 className={`red size${isActive ? ' active' : ''}`}>字符串拼接</h2>
</div>
)
}写法二:数组拼接
this.state = {
+ isActive: true
aClass: ['bd', 'active'],
}
render() {
const { isActive, aClass } = this.state
+ if(isActive) aClass.push('active')
return (
<div>
+ <div className={aClass.join(' ')}>绑定多个class-数组</div>
</div>
)
}写法三:第三方库 classnames
classnames 写法和 vue 很相似
绑定 style:绑定对象类型
this.state = {
+ oStyle: { color: 'red', backgroundColor: '#eee' }
}
return (
<div>
{/* 3. 绑定样式 */}
+ <div style={{ fontWeight: 600 }}>绑定样式</div>
+ <div style={oStyle}>绑定样式</div>
</div>
)事件绑定
如果原生 DOM原生有一个监听事件,我们可以如何操作呢?
方式一:获取 DOM 原生,添加监听事件;
方式二:在 HTML 原生中,直接绑定 onclick;
**在 React 中是如何操作呢?**我们来实现一下 React 中的事件监听,这里主要有两点不同
React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
我们需要通过*{}传入一个事件处理函数*,这个函数会在事件发生时被执行;
this 的绑定问题
在事件执行后,我们可能需要获取当前类的对象中相关的属性,这个时候需要用到 this
- 如果我们这里直接打印 this,也会发现它是一个 undefined
为什么是 undefined 呢?
原因是 btnClick 函数并不是我们主动调用的,而且当 button 发生改变时,React 内部调用了 btnClick 函数;
而它内部调用时,并不知道要如何绑定正确的 this;
如何绑定 this 呢?
方案一:bind()给 btnClick显式绑定 this
js<div>{ msg }</div> {/* 1. 通过bind(this)在绑定事件的时候传递this */} + <button onClick={this.onChangeMsg.bind(this)}>按钮1</button> // 方法 onChangeMsg() { console.log('this', this); this.setState({ msg: '你好世界' }) }方案二:使用 ES6 class fields 语法
jsreturn ( <div> <div>{ msg }</div> {/* 2. 通过es6的 class fields语法写函数 */} + <button onClick={this.onChangeMsg2}>按钮2</button> </div> ) + onChangeMsg2 = () => { console.log('this', this) this.setState({ msg: '你好,中国' }) }方案三:事件监听时传入箭头函数(个人推荐)
jsreturn ( <div> <div>{ msg }</div> {/* 3. 通过箭头函数绑定事件:(e) => this.onChangeMsg3(e) */} + <button onClick={(e) => this.onChangeMsg3(e)}>按钮3</button> </div> ) + onChangeMsg3(e) { console.log('this', this, e) this.setState({ msg: '你好,安徽' }) }
事件参数传递
在执行事件函数时,有可能我们需要获取一些参数信息:比如event 对象、其他参数
情况一:获取 event 对象
很多时候我们需要拿到 event 对象来做一些事情(比如阻止默认行为)
那么默认情况下,event 对象有被直接传入,函数就可以获取到 event 对象;
jsreturn ( <div> + <button onClick={(e) => this.onChangeMsg3(e)}>按钮3</button> </div> ) + onChangeMsg3(e) { console.log('this', this, e) }
情况二:获取更多参数
使用 bind 的方式的话,event 参数会被放在 btnClick 的参数列表的最后【不推荐】
js{/* 1. 通过bind的方法传递参数 */} + <button onClick={this.showUserInfo.bind(this, '刘备', 44, '女')}>修改个人信息</button> + showUserInfo(name, age, gender, e) { console.log('name:', name, 'age: ', age, 'gender: ', gender, 'this: ', this, 'e: ', e); }有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;
jsrender() { return ( <div> {/* 2. 通过箭头函数传递参数 */} + <button onClick={e => this.getPraram(e, '徐志摩', 22, '男')}>传递参数-箭头函数</button> </div> ) } + getPraram(e, name, age, gender) { + console.log('name:', name, 'age: ', age, 'gender: ', gender, 'this: ', this, 'e: ', e); + }
案例:电影列表的选中
// 组件-App
class App extends React.Component {
// 属性
constructor() {
super()
this.state = {
movies: ['阿凡达', '流浪地球', '外星人ET', '变形金刚', '速度与激情', '致青春'],
+ currIndex: 0
}
}
// 方法
+ onChange(index) {
+ this.setState({ currIndex: index })
+ }
// 渲染
render() {
const { movies, currIndex } = this.state
return (
<div>
<h3>电影列表</h3>
<ul>
{ movies.map((item, index) => {
return (
<li key={item}
+ className={currIndex === index ? 'active' : ''}
+ onClick={() => this.onChange(index)}>
{item}
</li>
)
}) }
</ul>
</div>
)
}
}
// 渲染
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)案例:电影列表的选中-抽取 1

案例:电影列表的选中-抽取 2

条件渲染
某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:
在 vue 中,我们会通过指令来控制:比如 v-if、v-show;
在React中,所有的条件判断都和普通的 JavaScript 代码一致;
常见的条件渲染的方式有哪些呢?
方式一:条件判断语句if
- 适合逻辑较多的情况
class App extends React.Component {
// 属性
constructor() {
super()
this.state = {
+ isReady: true
}
}
// 渲染
render() {
const { isReady } = this.state
// 条件渲染
+ let showEl
+ if(isReady) {
+ showEl = <h2>开始!</h2>
+ }else {
+ showEl = <h2>准备!</h2>
+ }
return (
<div>
+ <button onClick={() => this.setState({ isReady: !isReady })}>切换状态</button>
<hr/>
+ {showEl}
</div>
)
}
}方式二:三元运算符?:**
- 适合逻辑比较简单
return +(<div>{isReady ? <h2>开始!</h2> : <h2>准备!</h2>}</div>)方式三:与运算符&&**
- 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染;
使用场景: 当某个值有可能为 undefined / null 时,使用&& 进行条件判断
class App extends React.Component {
// 属性
constructor() {
super()
this.state = {
+ isTrue: true,
+ isNotNull: null,
+ isNotUndefined: undefined,
isNotEmpty: {},
isNotEmptyArray: []
+ friend: { name: 'Jack' }
}
}
// 渲染
render() {
const { isTrue, isNotNull, isNotUndefined, isNotEmpty, isNotEmptyArray } = this.state
return (
<div>
+ <div>{isTrue && <h2>开始!</h2>}</div>
+ <div>{!isNotNull && <h2>不为null</h2>}</div>
+ <div>{!isNotUndefined && <h2>不为undefined</h2>}</div>
<div>{!isNotEmpty && <h2>不为Empty</h2>}</div>
<div>{!isNotEmptyArray && <h2>不为EmptyArray</h2>}</div>
+ <div>{friend && <h2>{friend.name}</h2>}</div>
</div>
)
}
}
方式四:可选链??**
案例:v-if 效果

案例:v-show 效果
主要是控制 display 属性是否为 none
constructor() {
super()
this.state = {
+ isShow: true
}
}
// 渲染
render() {
const { isShow } = this.state
return (
<div>
+ <button onClick={() => this.setState({ isShow: !isShow })}>切换</button>
+ <div style={ { display: isShow ? 'block' : 'none' } }>显示</div>
</div>
)
}列表渲染
真实开发中我们会从服务器请求到大量的数据,数据会以列表的形式存储:
比如歌曲、歌手、排行榜列表的数据;
比如商品、购物车、评论列表的数据;
比如好友消息、动态、联系人列表的数据;
在 React 中并没有像 Vue 模块语法中的 v-for 指令,而且需要我们通过 JavaScript 代码的方式组织数据,转成 jsx:
很多从 Vue 转型到 React 的同学非常不习惯,认为 Vue 的方式更加的简洁明了;
但是 React 中的 jsx 正是因为和 JavaScript 无缝的衔接,让它可以更加的灵活;
另外我经常会提到 React 是真正可以提高我们编写代码能力的一种方式;
如何展示列表呢?
- 在 React 中,展示列表最多的方式就是使用数组的map高阶函数;
很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:
比如过滤掉一些内容:filter函数
比如截取数组中的一部分内容:slice函数
1、展示所有学生
class App extends React.Component {
// 属性
constructor() {
super()
this.state = {
aStudent: [
{ name: '张飞', age: 22, score: 78 },
{ name: '刘备', age: 25, score: 88 },
{ name: '关羽', age: 23, score: 98 },
{ name: '曹操', age: 27, score: 100 }
]
}
}
// 渲染
render() {
const { aStudent } = this.state
return (
<div>
+ {
// 1. 全部学生
+ aStudent.map(item => {
+ return (
+ <div className="item" key={item.name}>
+ <span>姓名:{item.name}</span>
+ <span>年龄:{item.age}</span>
+ <span>分数:{item.score}</span>
+ </div>
+ )
+ })
+ }
</div>
)
}
}2、只展示分数大于 90 的人,链式调用
filter(item => item.score > 90)
return (
<div>
{
// 2. 分数大于90的学生
+aStudent
.filter((item) => item.score > 90)
.map((item) => {
return (
<div className="item" key={item.name}>
<span>姓名:{item.name}</span>
<span>年龄:{item.age}</span>
<span>分数:{item.score}</span>
</div>
)
})
}
</div>
)3、分数大于 100,只展示前 2 个人的信息,链式调用
slice(0, 2)
return (
<div>
{
// 3. 分数大于90,展示前2位
+aStudent
.filter((item) => item.score > 90)
.slice(0, 2)
.map((item) => {
return (
<div className="item" key={item.name}>
<span>姓名:{item.name}</span>
<span>年龄:{item.age}</span>
<span>分数:{item.score}</span>
</div>
)
})
}
</div>
)列表中的 key
我们会发现在前面的代码中只要展示列表都会报一个警告:

这个警告是告诉我们需要在列表展示的 jsx 中添加一个 key。
key 主要的作用是为了提高 diff 算法时的效率;
这个我们在后续内容中再进行讲解;
aStudent.map((item) => {
return +(
<div className="item" key={item.name}>
<span>姓名:{item.name}</span>
<span>年龄:{item.age}</span>
<span>分数:{item.score}</span>
</div>
)
})jsx 的本质
实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。
- 所有的 jsx 最终都会被转换成 React.createElement 的函数调用。
createElement 需要传递三个参数:
参数一:type
当前 ReactElement 的类型;
如果是标签元素,那么就使用字符串表示 “div”;
如果是组件元素,那么就直接使用组件的名称;
参数二:props
所有 jsx 中的属性都在 props 中以对象的属性和值的形式存储;
比如传入 className 作为元素的 class;
参数三:children
存放在标签中的内容,以 children 数组的方式进行存储;
当然,如果是多个元素呢?React 内部有对它们进行处理,处理的源码在下方

createElement 源码

Babel 官网查看
我们知道默认 jsx 是通过 babel 帮我们进行语法转换的,所以我们之前写的jsx 代码都需要依赖 babel。
可以在 babel 的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react


直接编写 jsx 代码
我们自己来编写 React.createElement 代码:
我们就没有通过 jsx 来书写了,界面依然是可以正常的渲染。
另外,在这样的情况下,你还需要 babel 相关的内容吗?不需要了
- 所以,type="text/babel"可以被我们删除掉了;
- 所以,
<script src="../react/babel.min.js"></script>可以被我们删除掉了;
<div>
<div>Header</div>
<div>
Content
<div className="left">Left</div>
<div className="right">Right</div>
</div>
<div>Footer</div>
</div>转化成ReactElement的形式
const el = React.createElement(
'div',
null,
/*#__PURE__*/ React.createElement('div', null, 'Header'),
/*#__PURE__*/ React.createElement(
'div',
null,
'Content',
/*#__PURE__*/ React.createElement(
'div',
{
className: 'left'
},
'Left'
),
/*#__PURE__*/ React.createElement(
'div',
{
className: 'right'
},
'Right'
)
),
/*#__PURE__*/ React.createElement('div', null, 'Footer')
)
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(el)虚拟 DOM 的创建过程
我们通过 React.createElement 最终创建出来一个 ReactElement 对象:

这个 ReactElement 对象是什么作用呢?React 为什么要创建它呢?
原因是 React 利用 ReactElement 对象组成了一个JS 对象树;
JS 对象树就是虚拟 DOM(Virtual DOM);
如何查看 ReactElement 的树结构呢?
我们可以将之前的 jsx 返回结果进行打印;
注意下面代码中我打 jsx 的打印;
而 ReactElement 最终形成的树结构就是 Virtual DOM

ReactElement
属性:
- type:``,表示组件的类型(函数组件、类组件或原生 DOM 元素)
- key:``,用于帮助 React 在进行列表渲染时识别每个元素
- props:``,包含组件的属性(如 className)和子元素(children)
- ref:``,表示为对组件真正实例的引用
特性:
- 虚拟表示:ReactElement 是组件在内存中的虚拟表示,它描述了组件的结构和属性,但并不直接对应于实际的 DOM 元素。
- 不可变性:ReactElement 是不可变的,一旦创建就不能被修改。如果你想更新组件的状态或属性,需要创建一个新的 ReactElement。
JSX – 虚拟 DOM – 真实 DOM

虚拟 DOM 的作用:
- 1、通过 diff 算法,实现页面内容的局部更新
- 2、跨平台渲染(web,iOS, Andriod)
- 3、帮助我们从命令式编程转到了声明式编程的模式
声明式编程
虚拟 DOM 帮助我们从命令式编程转到了声明式编程的模式
**React 官方的说法:**Virtual DOM 是一种编程理念。
在这个理念中,UI 以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的 JavaScript 对象
我们可以通过 root.render 让 虚拟 DOM 和 真实 DOM 同步起来,这个过程中叫做协调(Reconciliation);
这种编程的方式赋予了 React 声明式的 API:
你只需要告诉 React 希望让 UI 是什么状态;
React 来确保 DOM 和这些状态是匹配的;
你不需要直接进行 DOM 操作,就可以从手动更改 DOM、属性操作、事件处理中解放出来;
关于虚拟 DOM 的一些其他内容,在后续的学习中还会再次讲到;
案例:书籍购物车
1.在界面上以表格的形式,显示一些书籍的数据;
2.在底部显示书籍的总价格;
3.点击+或者-可以增加或减少书籍数量(如果为 1,那么不能继续-);
4.点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~);

1、界面搭建
class App extends React.Component {
// 属性
constructor() {
super()
this.state = {
aBook: [
{ id: 0, name: '⟪三国演义⟫', pubDate: '2006-2', price: '86.00', count: 0 },
{ id: 1, name: '⟪水浒传⟫', pubDate: '2007-3', price: '76.00', count: 0 },
{ id: 2, name: '⟪西游记⟫', pubDate: '2002-11', price: '45.00', count: 0 },
{ id: 3, name: '⟪红楼梦⟫', pubDate: '2003-8', price: '78.00', count: 0 }
]
}
}
// 渲染
render() {
const { aBook } = this.state
return (
<div>
<table>
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{aBook.map((item, index) => {
return (
<tr>
<td>{item.id + 1}</td>
<td>{item.name}</td>
<td>{item.pubDate}</td>
<td>{item.price}</td>
<td>
<button> - </button>
<span className="count">{item.count}</span>
<button> + </button>
</td>
<td>
<button>移除</button>
</td>
</tr>
)
})}
</tbody>
</table>
<h3>总价格:333</h3>
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App />)2、计算总价格
const totalPrice = aBook.reduce((prevValue, item) => prevValue + item.price*item.count, 0)
<h3>总价格:{totalPrice}</h3>3、加减数量
注意: react 中不要直接修改 state 中的数组,对象内部的子元素,而是要对它进行浅拷贝后修改
// 加减购买数量
changeCount(index, pm) {
const aNewBook = [...this.state.aBook]
aNewBook[index].count += pm
this.setState({ aBook: aNewBook })
}
<td>
<button disabled={item.count <= 1 ? true : false} onClick={() => this.changeCount(index, -1)}> - </button>
<span className="count">{item.count}</span>
<button onClick={() => this.changeCount(index, 1)}> + </button>
</td>4、移除商品
// 移除书籍
removeBook(index) {
const aNewBook = [...this.state.aBook]
aNewBook.splice(index, 1)
this.setState({ aBook: aNewBook })
}
<td><button onClick={() => this.removeBook(index)}>移除</button></td>5、购物车为空提示
// 有书籍显示表格,没有书籍显示空提示
let elCart
if (aBook.length === 0) {
elCart = <div>空白购物车~</div>
} else {
elCart = (
<div>
<table>省略...</table>
<h3>总价格:{totalPrice}</h3>
</div>
)
}
return elCartReact 脚手架
认识脚手架工具
前端工程的复杂化
如果我们只是开发几个小的 demo 程序,那么永远不需要考虑一些复杂的问题:
比如目录结构如何组织划分;
比如如何管理文件之间的相互依赖;
比如如何管理第三方模块的依赖;
比如项目发布前如何压缩、打包项目;
等等...
现代的前端项目已经越来越复杂了:
不会再是在 HTML 中引入几个 css 文件,引入几个编写的 js 文件或者第三方的 js 文件这么简单;
比如 css 可能是使用 less、sass 等预处理器进行编写,我们需要将它们转成普通的 css 才能被浏览器解析;
比如 JavaScript 代码不再只是编写在几个文件中,而是通过模块化的方式,被组成在成百上千个文件中,我们需要通过模块化的技术来管理它们之间的相互依赖;
比如项目需要依赖很多的第三方库,如何更好的管理它们(比如管理它们的依赖、版本升级等);
为了解决上面这些问题,我们需要再去学习一些工具:
比如 babel、webpack、gulp,配置它们转换规则、打包依赖、热更新等等一些的内容;
脚手架的出现,就是帮助我们解决这一系列问题的;
脚手架是什么呢?
传统的脚手架指的是建筑学的一种结构:在搭建楼房、建筑物时,临时搭建出来的一个框架;

编程中提到的脚手架(Scaffold),其实是一种工具,帮我们可以快速生成项目的工程化结构;
每个项目作出完成的效果不同,但是它们的基本工程化结构是相似的;
既然相似,就没有必要每次都从零开始搭建,完全可以使用一些工具,帮助我们生产基本的工程化模板;
不同的项目,在这个模板的基础之上进行项目开发或者进行一些配置的简单修改即可;
这样也可以间接保证项目的基本结构一致性,方便后期的维护;
总结:脚手架让项目从搭建到开发,再到部署,整个流程变得快速和便捷;
create-react-app
缩写:CRA
前端脚手架
对于现在比较流行的三大框架都有属于自己的脚手架:
Vue 的脚手架:
基于 Webpack:@vue/cli;
基于 Vite: create-vue
Angular 的脚手架:@angular/cli
React 的脚手架:create-react-app
它们的作用都是帮助我们生成一个通用的目录结构,并且已经将我们所需的工程环境配置好。
使用这些脚手架需要依赖什么呢?
目前这些脚手架都是使用 node 编写的,并且都是基于 webpack的;
所以我们必须在自己的电脑上安装 node 环境;
这里我们主要是学习 React,所以我们以 React 的脚手架工具:create-react-app 作为讲解;
安装 node
React 脚手架本身需要依赖 node,所以我们需要安装 node 环境:
无论是 windows 还是 Mac OS,都可以通过 node 官网直接下载;
注意:这里推荐大家下载 LTS(Long-term support )版本,是长期支持版本,会比较稳定;

下载后,双击安装即可:
1.安装过程中,会自动配置环境变量;
2.安装时,会同时帮助我们安装 npm 管理工具;
安装 create-react-app
npm i create-react-app -g创建 React 项目
现在,我们就可以通过脚手架来创建 React 项目了。
创建 React 项目的命令如下:
注意:项目名称不能包含大写字母
另外还有更多创建项目的方式,可以参考 GitHub 的 readme
create-react-app <项目名称>创建完成后,进入对应的目录,就可以将项目跑起来:
cd 01-test-react
npm start
目录结构分析
我们可以通过 VSCode 打开项目:

了解 PWA
整个目录结构都非常好理解,只是有一个 PWA 相关的概念:
PWA全称Progressive Web App,即渐进式 WEB 应用;
一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用;
随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线缓存等功能;
这种 Web 存在的形式,我们也称之为是 Web App;
PWA 解决了哪些问题呢?
可以添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏;
实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能;
实现了消息推送;
等等一系列类似于 Native App 相关的功能;
更多 PWA 相关的知识,可以自行去学习更多;
https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps
webpack 配置
React 脚手架默认是基于 Webpack 来开发的;
但是,很奇怪:我们并没有在目录结构中看到任何 webpack 相关的内容?
- 原因是 React 脚手架将 webpack 相关的配置隐藏起来了(其实从 Vue CLI3 开始,也是进行了隐藏);
如果我们希望看到 webpack 的配置信息,应该怎么来做呢?
我们可以执行一个 package.json 文件中的一个脚本:
"eject": "react-scripts eject"这个操作是不可逆的,所以在执行过程中会给与我们提示;
npm run eject
webpack 的配置文件在 config/ 和 scripts/ 2 个目录中
脚手架中的 webpack

文件结构删除
通过脚手架创建完项目,很多同学还是会感觉目录结构过于复杂,所以我打算从零带着大家来编写代码。
我们先将不需要的文件统统删掉:
1.将 src 下的所有文件都删除
2.将 public 文件下出列 favicon.ico 和 index.html 之外的文件都删除掉

开始编写代码
在 src 目录下,创建一个index.js文件,因为这是webpack 打包的入口。
在 index.js 中开始编写 React 代码:
我们会发现和写的代码是逻辑是一致的;
只是在模块化开发中,我们需要手动的来导入 React、ReactDOM,因为它们都是在我们安装的模块中;
import ReactDOM from 'react-dom/cli'
import App from './App'
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App />)如果我们不希望直接在 root.render 中编写过多的代码,就可以单独抽取一个组件 App.js
import React from 'react'
class App extends React.Component {
constructor() {
super()
this.state = {
msg: 'Hello React'
}
}
render() {
const { msg } = this.state
return (
<div>
<div>App根组件{msg}</div>
</div>
)
}
}
export default App将 App 组件封装到 App.jsx 文件中
