一、React初体验
React是什么呢?
- 相信每个做开发的人对它都或多或少有一些印象;
- 这里我们来看一下官方对它的解释:用于构建用户界面的 JavaScript 库;
◼ 目前对于前端开发来说,几乎很少直接使用原生的JavaScript来开发应用程序,而是选择一个JavaScript库 (框架)。
- 在过去的很长时间内,jQuery是被使用最多的JavaScript库;
- 在过去的一份调查中显示,全球前10,000个访问最高的网站中,有65%使用了jQuery,是当时最受欢迎的JavaScript库;
- 但是,目前甚至已经处于淘汰的边缘了;
◼ **而无论是国内外,最流行的其实是三大框架:Vue、React、Angular。
如何学习React
React的介绍(技术角度)
React是什么?
- React:用于构建用户界面的 JavaScript 库;
- React的官网文档:zh-hans.reactjs.org/
React特点
◼ 声明式编程:
- 声明式编程是目前整个大前端开发的模式:Vue、React、Flutter、SwiftUI;
- 它允许我们只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面;
◼ 组件化开发:
- 组件化开发页面目前前端的流行趋势,我们会将复杂的界面拆分成一个个小的组件;
- 如何合理的进行组件的划分和设计也是后面我会讲到的一个重点;
◼ 多平台适配:
- 2013年,React发布之初主要是开发Web页面;
- 2015年,Facebook推出了ReactNative,用于开发移动端跨平台;(虽然目前Flutter非常火爆,但是还是有很多公司在使用ReactNative);
- 2017年,Facebook推出ReactVR,用于开发虚拟现实Web应用程序;(VR也会是一个火爆的应用场景);
React的开发依赖
◼ 开发React必须依赖三个库:
- react:包含react所必须的核心代码
- react-dom:react渲染在不同平台所需要的核心代码
- babel:将jsx转换成React代码的工具
◼ 第一次接触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管理(后续脚手架再使用)
◼ 暂时我们直接通过CDN引入,来演练下面的示例程序:
<!-- 添加依赖 -->
<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>
<!-- bable -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
Hello World
◼ 第一步:在界面上通过React显示一个Hello World
- 注意:这里我们编写React的script代码中,必须添加 type="text/babel",作用是可以让babel解析jsx的语法
◼ ReactDOM. createRoot函数:用于创建一个React根,之后渲染的内容会包含在这个根中
-
参数:将渲染的内容,挂载到哪一个HTML元素上
- 这里我们已经提定义一个id为app的div
◼ root.render函数:
- 参数:要渲染的根组件
◼ 我们可以通过{}语法来引入外部的变量或者表达式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hello React</title>
</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">
// 编写React代码(jsx语法)
// jsx语法 -> 普通的JavaScript代码 -> bable
const root = ReactDOM.createRoot(document.querySelector("#root"));
// 1.将文本定义成变量
let message = "hello world";
// 2.监听按钮的点击
function bntClick() {
// 1.修改数据
message = "Hello React";
// 2.重新渲染界面
rootRender();
}
rootRender();
// 3.封装一个渲染函数
function rootRender() {
root.render(
<div>
<h2>{message}</h2>
<button onClick={bntClick}>修改文本</button>
</div>
);
}
</script>
</body>
</html>
Hello React – 组件化开发
◼ 整个逻辑其实可以看做一个整体,那么我们就可以将其封装成一个组件:
- 我们说过root.render 参数是一个HTML元素或者一个组件;
- 所以我们可以先将之前的业务逻辑封装到一个组件中,然后传入到 ReactDOM.render 函数中的第一个参数;
◼ 在React中,如何封装一个组件呢? 这里我们暂时使用类的方式封装组件:
-
定义一个类(类名大写,组件的名称是必须大写的,小写会被认为是HTML元素),继承自React.Component
-
实现当前组件的render函数
- render当中返回的jsx内容,就是之后React会帮助我们渲染的内容
组件化 - 数据依赖
◼ 组件化问题一:* 数据在哪里定义***?**
◼ 在组件中的数据,我们可以分成两类:
- 参与界面更新的数据:当数据变量时,需要更新组件渲染的内容;
- 不参与界面更新的数据:当数据变量时,不需要更新将组建渲染的内容;
◼ 参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象的state中
-
我们可以通过在构造函数中 this.state = {定义的数据}
-
当我们的数据发生变化时,我们可以调用 this.setState 来更新数据,并且通知React进行update操作;
- 在进行update操作时,就会重新调用render函数,并且使用最新的数据,来渲染界面
class App extends React.Component {
// 组件数据
constructor() {
super();
this.state = {
message: "Hello World",
};
// 对需要绑定的方法,提前绑定好this
this.bntClick = this.bntClick.bind(this);
}
}
组件化 – 事件绑定
◼ 组件化问题二:事件绑定中的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>
重构Hello World
<!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>Document</title>
</head>
<body>
<div id="root"></div>
<script src="../lib/react.js"></script>
<script src="../lib//react-dom.js"></script>
<script src="../lib//bable.js"></script>
<script type="text/babel">
// 使用组件进行重构代码
// 类组件和函数式组件
class App extends React.Component {
// 组件数据
constructor() {
super();
this.state = {
message: "Hello World",
};
// 对需要绑定的方法,提前绑定好this
this.bntClick = this.bntClick.bind(this);
}
// 组件方法(实例方法)
bntClick() {
// console.log(this); undefined
// 内部完成了两件事:1.将state中的message值修改掉 2.自动执行重新执行render函数
this.setState({
message: "Hello React",
});
}
// 渲染内容render方法
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.bntClick}>修改文本</button>
</div>
);
}
}
// 将组件渲染到界面上
const root = ReactDOM.createRoot(document.querySelector("#root"));
// App根组件
root.render(<App />);
</script>
</body>
</html>
综合案例
电影列表展示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="../lib/react.js"></script>
<script src="../lib//react-dom.js"></script>
<script src="../lib//bable.js"></script>
<script type="text/babel">
// 1.创建root
const root = ReactDOM.createRoot(document.querySelector("#root"));
// 封装App组件
class App extends React.Component {
constructor() {
super();
this.state = {
movies: ["星际穿越", "流浪地球", "大话西游"],
};
}
render() {
// 1.对movies进行for循环
// const liEls = [];
// for (let i = 0; i < this.state.movies.length; i++) {
// const movie = this.state.movies[i];
// const liEl = <li>{movie}</li>;
// liEls.push(liEl);
// }
// 2. movies数组 =》 liEls数组
// const liEls = this.state.movies.map((movie) => <li>{movie}</li>);
return (
<div>
<h2>电影列表</h2>
<ul>
{this.state.movies.map((movie) => (
<li>{movie}</li>
))}
</ul>
</div>
);
}
}
// 2.渲染组件
root.render(<App />);
</script>
</body>
</html>
计数器案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="../lib/react.js"></script>
<script src="../lib/react-dom.js"></script>
<script src="../lib/bable.js"></script>
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector("#root"));
class App extends React.Component {
constructor() {
super();
this.state = {
number: 100,
};
}
render() {
const { number } = this.state;
return (
<div>
<h2>当前计数:{number}</h2>
<button onClick={this.increment.bind(this)}>+1</button>
<button onClick={this.decrement.bind(this)}>-1</button>
</div>
);
}
// 组件的方法
increment() {
this.setState({
number: this.state.number + 1,
});
}
decrement() {
this.setState({
number: this.state.number - 1,
});
}
}
root.render(<App />);
</script>
</body>
</html>
二、JSX语法
认识JSX
JSX是什么?
- JSX是一种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的书写规范:
-
JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div元素(或者使用后面我们学习的Fragment);
-
为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写;
-
JSX中的标签可以是单标签,也可以是双标签;
- 注意:如果是单标签,必须以/>结尾;
JSX的使用
◼ jsx中的注释
{/* jsx的注释写法 */}
◼ JSX嵌入变量作为子元素
-
情况一:当变量是Number、String、Array类型时,可以直接显示
-
情况二:当变量是null、undefined、Boolean类型时,内容为空;
- 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
- 转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;
-
情况三:Object对象类型不能作为子元素(not valid as a React child)
◼ JSX嵌入表达式
- 运算表达式
- 三元运算符
- 执行一个函数
◼ jsx绑定属性
- 比如元素都会有title属性
- 比如img元素会有src属性
- 比如a元素会有href属性
- 比如元素可能需要绑定class
- 比如原生使用内联样式style
<script type="text/babel">
// 1.定义根组件
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
title: "hhh",
imgUrl:
"https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fc90dcae96df46df906098437db738f0~tplv-k3u1fbpfcp-watermark.image?",
isActive: true,
ObjectStyle: { color: "red", fontSize: "30px" },
};
}
render() {
const { title, imgUrl, isActive, ObjectStyle } = this.state;
// 1.class绑定写法一:字符串的拼接
const className = `aaa bbb ${isActive ? "active" : ""}`;
// 2.class绑定写法二:将所有的class放到一个数组中
const classList = ["aaa", "bbb"];
if (isActive) classList.push("active");
// 3.class绑定的写法三:第三方库classnames
return (
<div>
{/* 1.基本绑定 */}
<h2 title={title}>我是H2元素</h2>
<img src={imgUrl} alt="" />
{/* 2.绑定class属性:最好使用className */}
<h2 className={className}>哈哈哈</h2>
<h2 className={classList.join(" ")}>哈哈哈哈</h2>
{/* 3.绑定style属性:绑定的是对象类型 */}
<h2 style={{ color: "red", fontSize: "30px" }}>哈哈哈哈哈哈</h2>
<h2 style={ObjectStyle}>哈哈哈哈哈哈</h2>
</div>
);
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
</script>
React事件绑定
◼ 如果原生DOM原生有一个监听事件,我们可以如何操作呢?
- 方式一:获取DOM原生,添加监听事件;
- 方式二:在HTML原生中,直接绑定onclick;
◼ 在React中是如何操作呢? 我们来实现一下React中的事件监听,这里主要有两点不同
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
- 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行;
<script type="text/babel">
// 1.定义根组件
class App extends React.Component {
constructor() {
super();
this.state = {
count: 100,
};
}
bnt1Click() {
this.setState({
count: "1000",
});
}
bnt2Click = () => {
this.setState({
count: "2000",
});
};
bnt3Click() {
this.setState({
count: "9999",
});
}
render() {
const { count } = this.state;
return (
<div>
{/* 1.this绑定方式一:bind绑定 */}
<button onClick={this.bnt1Click.bind(this)}>按钮</button>
{/* 2.this绑定方式二:ES6 class field */}
<button onClick={this.bnt2Click}>按钮</button>
{/* 3.this绑定方式三:直接传入一个箭头函数(zyao) */}
<button onClick={() => console.log("bnt3Click")}>按钮</button>
<button onClick={() => this.bnt3Click()}>按钮</button>
<h2>{count}</h2>
</div>
);
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
</script>
this的绑定问题
◼ 在事件执行后,我们可能需要获取当前类的对象中相关的属性,这个时候需要用到this
- 如果我们这里直接打印this,也会发现它是一个undefined
◼ 为什么是undefined呢?
- 原因是btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数;
- 而它内部调用时,并不知道要如何绑定正确的this;
◼ 如何解决this的问题呢?
- 方案一:bind给btnClick显示绑定this
- 方案二:使用 ES6 class fields 语法
- 方案三:事件监听时传入箭头函数(个人推荐)
<script type="text/babel">
// 1.定义根组件
class App extends React.Component {
constructor() {
super();
this.state = {
count: 100,
};
}
bnt1Click() {
this.setState({
count: "1000",
});
}
bnt2Click = () => {
this.setState({
count: "2000",
});
};
bnt3Click() {
this.setState({
count: "9999",
});
}
render() {
const { count } = this.state;
return (
<div>
{/* 1.this绑定方式一:bind绑定 */}
<button onClick={this.bnt1Click.bind(this)}>按钮</button>
{/* 2.this绑定方式二:ES6 class field */}
<button onClick={this.bnt2Click}>按钮</button>
{/* 3.this绑定方式三:直接传入一个箭头函数(zyao) */}
<button onClick={() => console.log("bnt3Click")}>按钮</button>
<button onClick={() => this.bnt3Click()}>按钮</button>
<h2>{count}</h2>
</div>
);
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
</script>
事件参数传递
◼ 在执行事件函数时,有可能我们需要获取一些参数信息:比如event对象、其他参数
◼ 情况一:获取event对象
- 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
- 那么默认情况下,event对象有被直接传入,函数就可以获取到event对象;
◼ 情况二:获取更多参数
- 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;
<script type="text/babel">
// 1.定义根组件
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
};
}
bntClick(event, name, age) {
console.log(event, this);
console.log(name, age);
}
render() {
const { message } = this.state;
return (
<div>
{/* 1.event参数的传递 */}
<button onClick={this.bntClick.bind(this)}>按钮1</button>
<button onClick={(event) => this.bntClick(event)}>按钮2</button>
{/* 2.额外的参数传递 */}
<button onClick={this.bntClick.bind(this, "kobe", 30)}>
按钮3(不推荐)
</button>
<button onClick={(event) => this.bntClick(event, "kobe", 30)}>
按钮4
</button>
</div>
);
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
</script>
React条件渲染
◼ 某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:
- 在vue中,我们会通过指令来控制:比如v-if、v-show;
- 在React中,所有的条件判断都和普通的JavaScript代码一致;
◼ 常见的条件渲染的方式有哪些呢?
◼ 方式一:条件判断语句
- 适合逻辑较多的情况
◼ 方式二:三元运算符
- 适合逻辑比较简单
◼ 方式三:与运算符&&
- 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染;
<script type="text/babel">
// 1.定义根组件
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
isReady: true,
friend: {
name: "kobe",
age: 30,
},
};
}
render() {
const { message, isReady, friend } = this.state;
// 1.条件判断一:使用if进行条件判断
let showElement = null;
if (isReady) {
showElement = <h2>准备开始比赛吧</h2>;
} else {
showElement = <h1>请提前做好准备</h1>;
}
return (
<div>
{/* 1.根据条件判断 */}
<div>{showElement}</div>
{/* 2.三元运算符 */}
<div>
{isReady ? <button>开始战斗</button> : <h3>赶快准备</h3>}
</div>
{/* 3.&&逻辑与的运算符 */}
{/* 场景:当取出来的friend可能为undefined时,使用&&进行条件判断 */}
<div>{friend && <div>{friend.name + " " + friend.age}</div>}</div>
</div>
);
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
</script>
◼ v-show的效果
- 主要是控制display属性是否为none
<script type="text/babel">
// 1.定义根组件
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
isShow: true,
};
}
changeShow() {
this.setState({
isShow: !this.state.isShow,
});
}
render() {
const { message, isShow } = this.state;
let showEl = null;
if (isShow) {
showEl = <h2>{message}</h2>;
}
return (
<div>
<button onClick={() => this.changeShow()}>切换</button>
{isShow && <h2>{message}</h2>}
{/* v-show的效果实现 */}
<h2 style={{ display: isShow ? "block" : "none" }}>哈哈哈哈哈</h2>
</div>
);
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
</script>
React列表渲染
◼ 真实开发中我们会从服务器请求到大量的数据,数据会以列表的形式存储:
- 比如歌曲、歌手、排行榜列表的数据;
- 比如商品、购物车、评论列表的数据;
- 比如好友消息、动态、联系人列表的数据;
◼ 在React中并没有像Vue模块语法中的v-for指令,而且需要我们通过JavaScript代码的方式组织数据,转成JSX:
- 很多从Vue转型到React的同学非常不习惯,认为Vue的方式更加的简洁明了;
- 但是React中的JSX正是因为和JavaScript无缝的衔接,让它可以更加的灵活;
- 另外我经常会提到React是真正可以提高我们编写代码能力的一种方式;
◼ 如何展示列表呢?
- 在React中,展示列表最多的方式就是使用数组的map高阶函数;
◼ 很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:
- 比如过滤掉一些内容:filter函数
- 比如截取数组中的一部分内容:slice函数
<script type="text/babel">
// 1.定义根组件
class App extends React.Component {
constructor() {
super();
this.state = {
students: [
{ id: 123, name: "why12", score: 199 },
{ id: 154, name: "why23", score: 91 },
{ id: 143, name: "why45", score: 195 },
{ id: 146, name: "why42", score: 198 },
],
};
}
render() {
const { message, students } = this.state;
// 分数大于100的学生显示
const filterStudents = students.filter((item) => {
return item.score > 100;
});
// 分数大于100,而且只显示2个人
const sliceStudents = filterStudents.slice(0, 2);
return (
<div>
<h2>学生列表数据</h2>
<div className="list">
{students
.filter((item) => {
return item.score > 100;
})
.slice(0, 2)
.map((item) => {
return (
<div className="item" key={item.id}>
<h2>学号:{item.id}</h2>
<h3>姓名:{item.name}</h3>
<h1>分数:{item.score}</h1>
</div>
);
})}
</div>
</div>
);
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
</script>
列表中的key
◼ 我们会发现在前面的代码中只要展示列表都会报一个警告:
◼ 这个警告是告诉我们需要在列表展示的jsx中添加一个key。
- key主要的作用是为了提高diff算法时的效率;
<div className="item" key={item.id}>
<h2>学号:{item.id}</h2>
</div>
JSX的本质
◼ 实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。
- 所有的jsx最终都会被转换成React.createElement的函数调用。
◼ createElement需要传递三个参数:
◼ 参数一:type
- 当前ReactElement的类型;
- 如果是标签元素,那么就使用字符串表示 “div”;
- 如果是组件元素,那么就直接使用组件的名称;
◼ 参数二:config
- 所有jsx中的属性都在config中以对象的属性和值的形式存储;
- 比如传入className作为元素的class;
◼ 参数三:children
- 存放在标签中的内容,以children数组的方式进行存储;
- 当然,如果是多个元素呢?React内部有对它们进行处理,处理的源码在下方
直接编写jsx代码
◼ 我们自己来编写React.createElement代码:
-
我们就没有通过jsx来书写了,界面依然是可以正常的渲染。
-
另外,在这样的情况下,你还需要babel相关的内容吗?不需要了
- 所以,type="text/babel"可以被我们删除掉了;
- 所以,< script src="../react/babel.min.js">< /script>可以被我们删除掉了;
<script>
// 1.定义根组件
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
};
}
render() {
const { message } = this.state;
const element = /*#__PURE__*/ React.createElement(
"div",
null,
/*#__PURE__*/ React.createElement(
"div",
{
className: "header",
},
"Header"
),
/*#__PURE__*/ React.createElement(
"div",
{
className: "content",
},
/*#__PURE__*/ React.createElement("div", null, "Banner"),
/*#__PURE__*/ React.createElement(
"ul",
null,
/*#__PURE__*/ React.createElement(
"li",
null,
"\u5217\u8868\u6570\u636E1"
),
/*#__PURE__*/ React.createElement(
"li",
null,
"\u5217\u8868\u6570\u636E2"
),
/*#__PURE__*/ React.createElement(
"li",
null,
"\u5217\u8868\u6570\u636E3"
),
/*#__PURE__*/ React.createElement(
"li",
null,
"\u5217\u8868\u6570\u636E4"
),
/*#__PURE__*/ React.createElement(
"li",
null,
"\u5217\u8868\u6570\u636E5"
),
/*#__PURE__*/ React.createElement(
"li",
null,
"\u5217\u8868\u6570\u636E6"
)
)
),
/*#__PURE__*/ React.createElement(
"div",
{
className: "footer",
},
"Footer"
)
);
return element;
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(React.createElement(App, null));
</script>
什么是虚拟DOM?虚拟DOM在React中起到什么作用?
- 虚拟DOM diff
- 跨平台渲染
- 声明式编程
虚拟DOM的创建过程
◼ 我们通过 React.createElement 最终创建出来一个 ReactElement对象:
◼ 这个ReactElement对象是什么作用呢?React为什么要创建它呢?
- 原因是React利用ReactElement对象组成了一个JavaScript的对象树;
- JavaScript的对象树就是虚拟DOM(Virtual DOM);
◼ 如何查看ReactElement的树结构呢?
- 我们可以将之前的jsx返回结果进行打印;
- 注意下面代码中我打jsx的打印;
◼ 而ReactElement最终形成的树结构就是Virtual DOM;
声明式编程
◼ 虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
◼ React官方的说法: Virtual DOM 是一种编程理念。
- 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
- 我们可以通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation);
◼ 这种编程的方式赋予了React声明式的API:
- 你只需要告诉React希望让UI是什么状态;
- React来确保DOM和这些状态是匹配的;
- 你不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来;
阶段案例练习
◼ 1.在界面上以表格的形式,显示一些书籍的数据;
◼ 2.在底部显示书籍的总价格;
◼ 3.点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续-);
◼ 4.点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~);
<!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>
<style>
table {
border-collapse: collapse;
text-align: center;
}
thead {
background-color: #f2f2f2;
}
th,
td {
padding: 10px 16px;
border: 1px solid #aaa;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="../lib/react.js"></script>
<script src="../lib/react-dom.js"></script>
<script src="../lib/bable.js"></script>
<script src="./data.js"></script>
<script src="./format.js"></script>
<script type="text/babel">
// 1.定义根组件
class App extends React.Component {
constructor() {
super();
this.state = {
books: books,
};
}
getTotalPrice() {
const totalPrice = this.state.books.reduce((preValue, item) => {
return preValue + item.count * item.price;
}, 0);
return totalPrice;
}
increment(index) {
const newBooks = [...this.state.books];
newBooks[index].count += 1;
this.setState({
books: newBooks,
});
}
decrement(index) {
const newBooks = [...this.state.books];
newBooks[index].count -= 1;
this.setState({
books: newBooks,
});
}
changeCount(index, count) {
const newBooks = [...this.state.books];
newBooks[index].count += count;
this.setState({
books: newBooks,
});
}
removeItem(index) {
const newBooks = [...this.state.books];
newBooks.splice(index, 1);
this.setState({
books: newBooks,
});
}
renderBookList() {
const { books } = this.state;
return (
<div>
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{books.map((item, index) => {
return (
<tr key={item.id}>
<td>{index + 1}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>{formatPrice(item.price)}</td>
<td>
<button
disabled={item.count <= 1}
onClick={() => this.changeCount(index, -1)}
>
-
</button>
{item.count}
<button onClick={() => this.changeCount(index, 1)}>
+
</button>
</td>
<td>
<button onClick={() => this.removeItem(index)}>
删除
</button>
</td>
</tr>
);
})}
</tbody>
</table>
<h2>总价格:{formatPrice(this.getTotalPrice())}</h2>
</div>
);
}
renderBookEmpty() {
return (
<div>
<h2>购物车为空,请添加书籍</h2>
</div>
);
}
render() {
const { books } = this.state;
// 1.计算总价格方式一:
// let totalPrice = 0;
// for (let i = 0; i < books.length; i++) {
// const book = books[i];
// totalPrice += book.price * book.count;
// }
// 2.计算总价格的方式二:
// const totalPrice = books.reduce((preValue, item) => {
// return preValue + item.count * item.price;
// }, 0);
return books.length ? this.renderBookList() : this.renderBookEmpty();
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
</script>
</body>
</html>