React特点
声明式编程
- 声明式编程是目前整个大前端开发的模式:Vue、React、Flutter、SwiftUI
- 它允许我们只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面
组件化开发
- 组件化开发页面是目前前端的流行趋势,我们会将复杂的页面拆分成一个个小的组件
- 如何合理的进行组件的划分和设计也是重点
多平台适配
- React主要是用于开发Web页面
- ReactNative用于开发移动端跨平台
- ReactVR用于开发虚拟现实Web应用
案例
在页面显示一个文本 Hello World
点击下方的一个按钮,点击之后文本改变为Hello React
原生代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h2 class="title"></h2>
<button class="btn">改变文本</button>
<script>
// 命令式编程
// 1.定义数据
let message = "hello world"
// 2.将数据显示在h2元素中
const titleEl = document.getElementsByClassName("title")[0]
titleEl.innerHTML = message
// 3.点击按钮,界面数据发生改变
const btnEl = document.getElementsByClassName("btn")[0]
btnEl.addEventListener("click", e => {
message = "hello react"
titleEl.innerHTML = message
})
</script>
</body>
</html>
React开发
React开发依赖
- 必须依赖三个库
- react:包含react所必须的核心代码
- react-dom:react渲染在
不同平台
所需的核心代码 - babel:将jsx转换成React代码的工具
- Vue只需要一个Vue.js文件即可,但是React需要依赖三个库
- 0.14版本之前没有react-dom,所有功能都包含的在react中
- 这三个库各司其职
- 拆分的原因:react-native
- react中包含了react和react-native所共有的核心代码
- react-dom针对web和native所完成的事情不同
- web端:react-dom会将jsx最终渲染成真实的DOM,显示在浏览器中
- native端:react-dom会将jsx最终渲染成原生的控件(如Android中的Button,IOS中的UIButton)
Babel
- Babel是什么
- Babel又名Babel.js
- 是目前前端使用非常广泛的编译器、转移器
- 当下很多浏览器不支持ES6语法,但是ES6的语法非常的简洁方便
- 编写源码时可以使用ES6来编写,之后通过Babel工具将ES6转换成大多数浏览器都支持的ES5语法
- React和Babel的关系
- 默认情况下开发React可以不使用babel
- 自己需要写React.createElement来编写代码,非常的繁琐,可读性也非常差
- 所以我们可以直接编写jsx(JavaScript XML)的语法,通过Babel来转换成React.createElement
引入React依赖
- 添加依赖的三种方式
- 直接通过CDN引入
- 下载到本地,添加本地依赖
- 通过npm管理
暂时可以通过CDN进行引入
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
这里的crossorigin属性,是为了拿到跨域脚本的错误信息
用React实现Hello World
- 使用jsx,并且希望script中的jsx的代码被解析,必须在script标签中添加一个属性
text/babel
- ReactDOM.render
- 参数一:要渲染的内容,可以是HTML元素,也可以是React组件
- 参数二:要将渲染分内容挂载到哪一个HTML元素上
- 通过
{}
语法来引入外部的变量或者表达式,在Vue中使用的{{}}
,需要注意区分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 添加React依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<div id="app"></div>
<!-- 开始开发 -->
<!-- 注意事项:使用jsx,并且希望script中的jsx的代码被解析,必须在script标签中添加一个属性 text/babel -->
<script type="text/babel">
// ReactDOM.render(渲染的内容, 挂载的对象)
let message = "Hello World"
// Vue中是{{}},React中是{}
ReactDOM.render(<h2>{message}</h2>,document.getElementById('app'))
</script>
</body>
</html>
添加按钮,实现文本改变
错误语法
jsx特点:多个组件最外层(根)只能有一个组件
ReactDOM.render(
<h2>{message}</h2>
<button>改变文本</button>,
document.getElementById('app'))
正确语法
ReactDOM.render(
<div>
<h2>{message}</h2>
<button>改变文本</button>
</div>,
document.getElementById('app'))
添加点击事件
创建点击触发事件function
function btnClick() {
console.log("按钮发生点击")
message = "Hello React"
}
绑定点击事件
ReactDOM.render(
<div>
<h2>{message}</h2>
<button onClick={btnClick}>改变文本</button>
</div>,
document.getElementById('app'))
点击页面上的按钮,发现控制台有日志打印,但是页面的文本并没有改变
在Vue中,绑定的元素发生改变,页面数据会立即跟着改变,但是在React中,需要重新渲染DOM
最终代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 添加React依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<div id="app"></div>
<!-- 开始开发 -->
<!-- 注意事项:使用jsx,并且希望script中的jsx的代码被解析,必须在script标签中添加一个属性 text/babel -->
<!-- jsx特点:多个组件最外层(根)只能有一个组件 -->
<script type="text/babel">
// ReactDOM.render(渲染的内容, 挂载的对象)
let message = "Hello World"
// Vue中是{{}},React中是{}
// 会报错 多个组件最外层(根)只能有一个组件
/*ReactDOM.render(
<h2>{message}</h2>
<button>改变文本</button>,
document.getElementById('app'))*/
// Vue中message发生改变时页面会发生改变,react中需要重新渲染DOM
function btnClick() {
console.log("按钮发生点击")
message = "Hello React"
render()
}
function render(){
ReactDOM.render(
<div>
<h2>{message}</h2>
<button onClick={btnClick}>改变文本</button>
</div>,
document.getElementById('app'))
}
render()
</script>
</body>
</html>
组件化开发
整个逻辑可以看做是一个整体,那么就可以将它封装成一个组件
ReactDOM.render的第一个参数是一个HTML或者一个组件
所以可以将之前的业务逻辑封装到一个组件中,然后传入到ReactDOM.render函数的第一个参数
封装组件
暂时使用类的方式封装组件
- 定义一个类(类名大写,组件的名称必须是大写的,小写会被认为是HTML元素),继承自React.Component
- 实现当前组件的render函数,render中返回的jsx内容就是之后React去渲染的内容
class App extends React.Component {
render() {
return(<h2>Hello World</h2>)
}
}
ReactDOM.render(<App/>,document.getElementById('app'))
数据依赖
组件中的数据,可以分为两类
- 参与界面更新的数据:当数据改变时,需要重新将组件渲染的内容
- 不参与界面更新的数据:当数据改变时,不需要更新将组件渲染的内容
参与界面更新的数据可以称之为参与数据流,这个数据是定义在当前对象的state中
- 可以通过在构造函数中
this.state={定义的数据}
- 当数据发生变化时,可以调用
this.setState
来更新数据,并且通知React进行更新操作 - 在进行更新操作时,会重新调用render函数,并且使用最新的数据来渲染页面
class App extends React.Component {
constructor() {
super()
this.state = {
message = "Hello World"
}
}
}
事件绑定
事件绑定中的this
在类中直接定义一个函数,并且将这个函数绑定到HTML原生的onClick事件上,当前这个函数的this指向的是谁?
class App extends React.Component {
constructor() {
super();
this.state = {
message : 'Hello world'
}
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.btnClick}>改变文本</button>
</div>
)
}
btnClick() {
this.setState({
message: 'Hello React'
})
}
}
默认情况下,btnClick中的this是undefined
- 在正常的DOM操作中,监听点击,监听函数中的this其实就是节点对象(比如button对象)
- React并不是直接渲染成真实的DOM,我们所编写的button只是一个语法糖,它的本质上是React的Element对象
- 那么在这里发生监听的时候,react给我们的函数绑定的this,默认情况下就是undefined
我们在绑定的函数中,可能想要使用当前对象,比如执行this.setState函数,就必须拿到当前对象的this
- 需要在传入函数时,给这个函数直接绑定this
- 例如
</button onClick={this.btnClick.bind(this)}>改变文本</button>
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 添加React依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<div id="app"></div>
<!-- 开始开发 -->
<!-- 注意事项:使用jsx,并且希望script中的jsx的代码被解析,必须在script标签中添加一个属性 text/babel -->
<!-- jsx特点:多个组件最外层(根)只能有一个组件 -->
<script type="text/babel">
// 封装APP组件
class App extends React.Component {
constructor() {
super();
this.state = {
message : 'Hello world'
}
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.btnClick.bind(this)}>改变文本</button>
</div>
)
}
btnClick() {
this.setState({
message: 'Hello React'
})
}
}
// 渲染组件
ReactDOM.render(<App/>, document.getElementById('app'))
</script>
</body>
</html>
ES6的class
在ES6之前,通过function来定义类,但是这种模式一直被很多从其他编程转到JavaScript的人所不适应,因为大多数面向对象的语言,都是使用class关键字来定义类的。
JavaScript从ES6开始引入了class关键字,用于定义一个类。
// ES5中如何定义类
function Person(name,age){
this.name = name
this.age = age
}
// 定义方法
// 写在原型上
Person.prototype.running = function (){
console.log(this.name,this.age,"running")
}
// 通过new创建对象
// 创建时this绑定到我们新创建的对象上
var person = new Person("why",18);
console.log(person.name,person.age)
person.running(); // 隐式绑定
/ ES6中通过class创建类
class Person {
// 构造方法
constructor(name, age) {
this.name = name
this.age = age
}
// 定义方法
running() {
console.log(this)
console.log(this.name, this.age, "running")
}
}
const p = new Person("why", 18);
console.log(p.name, p.age)
// this绑定题目
let func = p.running
// 报错,this是undefined
// func();
var obj = {
name:"kobe",
age:40
}
func.call(obj)
// 给func重新复制
func = func.bind(obj)
func()
类中有一个constructor构造方法,当我们通过new关键字调用时,就会默认执行这个构造方法
构造方法中可以给当前对象添加属性
类中也可以定义其他方法,这些方法会被放到类的prototypy上
另外,属性也可以直接定义在类上
继承是面向对象的一大特性,可以减少我们重复代码的编写,方便公共内容的抽取。
ES6中增加了extends关键字来作为类的继承
在constructor中,子类必须通过super来调用父类的构造方法,对父类进行初始化,否则会报错。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
/**
* 面向对象三大特性:封装、继承、多态
// */
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
running() {
console.log("running")
}
}
class Student extends Person{
constructor(name,age,sno) {
super(name,age);
this.sno = sno;
}
}
let student = new Student("young",18,110);
console.log(student.name,student.age,student.sno)
student.running()
class Teacher extends Person{
constructor(name,age,sno) {
// 子类必须初始化父类对象
super(name,age);
this.sno = sno;
}
}
/* class Student {
constructor(name, age, sno) {
this.name = name
this.age = age
this.sno = sno
}
running() {
console.log("running")
}
}
class Teacher {
constructor(name, age, title) {
this.name = name
this.age = age
this.title = title
}
running() {
console.log("running")
}
}*/
</script>
</body>
</html>
数组的map方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const names = ["abc","cba","nba","mba"]
// names.map(回调函数,给前面的回调函数绑定this)
/**
* 回调函数有三个参数
* 1:执行时的对应元素
* 2:对应的下标值
* 3. 完整的数组对象
*
* map会返回一个新的数组
*/
const newNames = names.map((item,index,arr)=>{
return item + '000'
})
console.log(newNames)
</script>
</body>
</html>
案例练习
电影列表展示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<!-- 2.编写react语法 -->
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: ["大华西游", "盗梦空间", "流浪地球", "星际穿越"]
}
}
render() {
// 通过声明数组,并且用push的方式
const liArray = [];
for (let movie of this.state.movies) {
liArray.push(<li>{movie}</li>)
}
return (
<div>
<h2>电影列表1</h2>
<ul>
{liArray}
</ul>
<h2>电影列表2</h2>
<ul>
{
this.state.movies.map((item) => {
return <li>{item}</li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
计数器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>当前计数:{this.state.counter}</h2>
<button onClick={this.increment.bind(this)}>+1</button>
<button onClick={this.decrement.bind(this)}>-1</button>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
decrement() {
this.setState({
counter: this.state.counter - 1
})
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
JSX
const element = <h2>Hello word</h2>
ReactDOM.render(element, document.getElementById("app"))
这段element变量的声明右侧赋值的标签语法
- 它不是一段字符串,没有使用引号包裹,看起来像一段html
- 正常情况下这么写是不可以的,如果将type="text/babel"去掉,就会出现语法错误
JSX是什么
- JSX是一种JavaScript的语法扩展(extension),也在很多地方称之为JavaScript XML
- 它用于描述我们的UI界面,并且其完全可以和JavaScript融合在一起使用
- 它不同于Vue中的模块语法,不需要专门学习模块语法中的一些指令(v-if,v-for,v-else,v-bind)
React认为渲染逻辑本质上与其他UI逻辑存在内在耦合
- 比如UI需要绑定时间(button,a原生标签等)
- 比如UI中需要展示数据状态,在某些状态发生改变时,又需要改变UI
他们之间是密不可分的,所以react没有将标记分离到不同的文件中,而是组合到到了一起,这个地方就是组件(Component)
JSX其实是嵌入到JavaScript中的一种结构语法。
JSX的书写规范
- JSX的顶层只能有一个根元素,所以很多时候我们会在外层包裹一个div原生(或者后面学习的Fragment)
- 为了方便乐队,通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写
- jsx中的标签可以是单标签,也可以双标签,如果是单标签,必须以/>结尾
jsx中的注释
{/*我是注释*/}
jsx嵌入变量
- 情况一:当变量是number、string、array类型时,可以直接显示
- 情况二:当变量是null、undefined、boolean类型时,内容为空
- 如果希望可以显示null、undefined、boolean,那么需要转换成字符串
- 可以调用toString()方法,或者直接拼接字符串,或者String(变量)等方式
- 情况三:对象类型不能作为子元素(not valid as a React child)
jsx嵌入表达式
- 可以是运算表达式,如数字计算,字符串拼接等
- 可以是三目表达式
- 可以是一个函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
// 在{}可以正常显示
name: "why",
age: 18,
names: ["abc", "cba", "nba"],
// 在{}不能显示(忽略)
test1: null,
test2: false,
test3: undefined,
flag: true,
// 对象不能作为jsx的子类
friend :{
name: "kobe",
age: 40
}
}
}
render() {
return (
<div>
<h2>{this.state.name}</h2>
<h2>{this.state.age}</h2>
<h2>{this.state.names}</h2>
<h2>{this.state.test1}</h2>
<h2>{this.state.test2}</h2>
<h2>{this.state.test3}</h2>
<h2>{this.state.flag && "您好"}</h2>
<h2>{this.state.flag ? "您好" : null}</h2>
{/* 会报错 */}
<h2>{this.state.friend}</h2>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
JSX的使用
JSX绑定属性
- 比如元素都会有title属性
- 比如img元素会有src属性
- 比如a元素会有herf属性
- 比如元素可能需要绑定class
- 比如原生使用内联样式style
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<div style="color: red; font-size: 30px;"></div>
<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script type="text/babel">
function getSizeImage(imgUrl, size) {
return imgUrl + `?param=${size}x${size}`
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "标题",
imgUrl: "http://p2.music.126.net/L8IDEWMk_6vyT0asSkPgXw==/109951163990535633.jpg",
link: "http://www.baidu.com",
active: true
}
}
render() {
const {title, imgUrl, link, active} = this.state
return (
<div>
{/*绑定普通属性*/}
<h2 title={title}>我是标题</h2>
<img src={getSizeImage(imgUrl, 140)} alt=""/>
<a href={link} target="_blank">百度一下</a>
{/* 绑定class
class要写成className
*/}
<div className="box title">我是div元素</div>
<div className={"box title " + (active ? "active" : "")}>我也是div元素</div>
<label htmlFor=""></label>
{/* 绑定style*/}
{/* 第一层括号表示使用jsx绑定,第二层表示使用一个对象
属性red要用引号引起来,否则会认为是被定义的属性,属性名要使用驼峰命名
*/}
<div style={{color: "red", fontSize: "50px"}}>我是div,绑定style属性</div>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
事件绑定
- 如果原生DOM原生有一个监听事件,我们如何操作
- 方式一:获取DOM原生,添加监听时间
- 方式二:在HTML原生中,直接绑定onClick
- 在React中如何操作
- 在React事件的命名采用小驼峰(lowCamelCase),而不是纯小写
- 需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行
this的绑定问题
- 事件执行后,我们可坑需要获取当前类的对象中的相关属性,这个时候需要用到this
- 如果这里直接打印this,会发现它是一个undefined
- 为什么是undefined
- 原因是btnClick函数并不是我们主动调用的,而是当button发生改变,React内部调用了btnClick函数
- 而它内部调用时,并不知道如何绑定正确的this
解决this的问题
- 方案一:bind给btnClick显示绑定this
- 方案二:使用ES6的class field语法
- 方案三:事件监听时传入箭头函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "你好",
counter: 100
}
this.btnClick = this.btnClick.bind(this)
}
render() {
return (
<div>
{/* 方案一:bind绑定this(显示绑定)*/}
<button onClick={this.btnClick}>按钮1</button>
{/* 方案二:定义函数式,使用箭头函数*/}
<button onClick={this.increment}>+1</button>
{/* 方案三:直接传入一个箭头函数,在箭头函数中调用需要执行的函数*/}
<button onClick={() => {
this.decrement("young")
}}>-1
</button>
</div>
)
}
btnClick() {
console.log(this.state.message);
}
// 箭头函数中永远不绑定this,会向上查找最近的this
// ES中给对象增加属性 class field
increment = () => {
console.log(this.state.counter);
}
decrement(name) {
console.log(this.state.counter, name)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
事件参数传递
- 在执行事件函数时,有可能我们需要获取一些参数信息,比如event对象、其他参数
- 情况一:获取event对象
- 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
- 假如我们用不到this,那么直接传入函数就可以获取到event对象
- 情况二:获取更多参数
- 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: ["大话西游", "钢铁侠", "梦幻西游", "蜘蛛侠"]
}
this.btnClick = this.btnClick.bind(this)
}
render() {
return (
<div>
<button onClick={this.btnClick}>按钮</button>
<ul>
{
this.state.movies.map((item, index, arr) => {
return (
<li className="item"
onClick={event => {
this.liClick(item, index, event)
}}
title="li">
{item}
</li>
)
})
}
</ul>
</div>
)
}
btnClick(event) {
console.log("按钮发生了点击", event)
}
liClick(item, index, event) {
console.log("li发生了点击", item, index, event)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
条件渲染
某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染部分内容
- 在vue中,我们可以通过指令来控制,比如v-if,v-show
- 在react中,所有的条件判断都和普通的JavaScript代码一致
常见的条件渲染的方式
- 条件判断语句,适合逻辑较多的情况
- 三目运算符,适合逻辑比较简单的情况
- 逻辑与运算符&&,适合如果条件成立,渲染一个组件;如果条件不成立,什么内容都不渲染
- v-show的效果。主要是控制display属性是否为none
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLogin: true
}
}
render() {
const {isLogin} = this.state
// 通过if判断:逻辑代码非常多的情况
let welcome = null;
let btnText = null
if (isLogin) {
welcome = <h2>欢迎回来</h2>
btnText = "退出"
} else {
welcome = <h2>请先登录</h2>
btnText = "登录"
}
return (
<div>
<div>我是div元素</div>
{welcome}
{/* 三目运算符*/}
<button onClick={e => {
this.loginClick()
}}>{isLogin ? "退出" : "登录"}</button>
<br/>
<h2>{isLogin ? "你好啊" : null}</h2>
{/* 逻辑与&&*/}
<h2>{isLogin && "你好啊"}</h2>
{isLogin && <h2>你好啊</h2>}
</div>
)
}
loginClick() {
this.setState({
isLogin: !this.state.isLogin
})
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLogin: true
}
}
render() {
const {isLogin} = this.state
const titleDisplayValue = isLogin ? "block" : "none"
return (
<div>
<button onClick={e => this.loginClick()}>{isLogin ? "退出" : "登录"}</button>
<h2 style={{display: titleDisplayValue}}>你好啊</h2>
</div>
)
}
loginClick() {
this.setState({
isLogin: !this.state.isLogin
})
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
列表渲染
真实开发中,我们会从服务器请求到大量的数据,数据会以列表的形式存储
- 比如歌曲、歌手、排行榜列表的数据
- 比如商品、购物车、评论列表的数据
- 比如好友消息、动态、联系人列表的数据
在React中并没有Vue模块语法中的v-for指令,而且需要我们通过JavaScript代码的方式组织数据,转成JSX
- 很多从vue转型到react的同学非常不习惯,认为vue的方式更加简洁明了
- 但是React中的JSX正是因为和JavaScript无缝的衔接,让它可以更加的灵活
在React中,展示列表最多的方式就是使用数组的map高阶函数
很多时候,我们在展示一个数组中的数据之前,需要先对它进行一些处理
- 比如过滤掉一些内容:filter函数
- 比如截取数组中的一部分内容:slice函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
names: ["abc", "cbd", "nba", "mba", "dna"],
numbers: [110, 123, 45, 32, 50, 10, 55, 8, 666]
}
}
render() {
return (
<div>
<h2>名字列表</h2>
<ul>
{
this.state.names.map(item => {
return <li>{item}</li>
})
}
</ul>
<h2>数字列表(过滤)</h2>
<ul>
{
this.state.numbers.filter(item => item >= 50).map(item => <li>{item}</li>)
}
</ul>
<h2>数字列表(截取)</h2>
<ul>
{
this.state.numbers.slice(0, 4).map(item => <li>{item}</li>)
}
</ul>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
JSX的本质
jsx仅仅是React.createElement(component, props, …children)函数的语法糖
所有的jsx最终都会被转换成React.createElement的函数调用
React.createElement在源码的packages–>react–>src–>ReactElement.js文件
createElement需要传递三个参数
- type
- 当前ReactElement的类型
- 如果是标签元素,那么就用字符串表示,如
"div"
- 如果是组件元素,那么就直接使用组件的名字
- config
- 所有jsx中的属性都在config中以对象的属相和值的形式存储
- children
- 存放在标签中的内容,以children数组的方式进行存储
- 如果是多个元素,React内部有对它们进行处理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="../react/react.development.js"></script>
<script src="../react/react-dom.development.js"></script>
<script src="../react/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div>
<div className="header">
<h1 title="标题">我是标题</h1>
</div>
<div className="content">
<h2>我是页面的内容</h2>
<button>按钮</button>
<button>+1</button>
<a href="http://www.baidu.com">百度一下</a>
</div>
<div className="footer">
<p>我是尾部的内容</p>
</div>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
Babel
默认jsx是通过babel帮助我们进行语法转换的,所以我们之前写的jsx代码都需要babel进行转换
可以在babel的官网中快速查看转换的过程https://babeljs.io/repl/#?presets=react
直接编写jsx代码
如果我们自己来编写React.createElement代码,就可以不使用babel相关的内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="../react/react.development.js"></script>
<script src="../react/react-dom.development.js"></script>
<script src="../react/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
className: "header"
}, /*#__PURE__*/React.createElement("h1", {
title: "\u6807\u9898"
}, "\u6211\u662F\u6807\u9898")), /*#__PURE__*/React.createElement("div", {
className: "content"
}, /*#__PURE__*/React.createElement("h2", null, "\u6211\u662F\u9875\u9762\u7684\u5185\u5BB9"), /*#__PURE__*/React.createElement("button", null, "\u6309\u94AE"), /*#__PURE__*/React.createElement("button", null, "+1"), /*#__PURE__*/React.createElement("a", {
href: "http://www.baidu.com"
}, "\u767E\u5EA6\u4E00\u4E0B")), /*#__PURE__*/React.createElement("div", {
className: "footer"
}, /*#__PURE__*/React.createElement("p", null, "\u6211\u662F\u5C3E\u90E8\u7684\u5185\u5BB9")));
}
}
ReactDOM.render(<App />, document.getElementById("app"));
</script>
</body>
</html>
虚拟DOM
虚拟DOM的创建过程
我们通过React.createElement最终创建出来一个ReactElement对象
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props
)
React利用ReactElement对象组成了一个JavaScript对象树
这个JavaScript的对象树就是虚拟DOM(Virtual DOM)
我们可以将之前的jsx返回结果进行打印,就可以查看到ReactElement对象树
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="../react/react.development.js"></script>
<script src="../react/react-dom.development.js"></script>
<script src="../react/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
// jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 真实DOM
// jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 原生的控件(UIButton/Button)
var elementObj = (
<div>
<div className="header">
<h1 title="标题">我是标题</h1>
</div>
<div className="content">
<h2>我是页面的内容</h2>
<button>按钮</button>
<button>+1</button>
<a href="http://www.baidu.com">百度一下</a>
</div>
<div className="footer">
<p>我是尾部的内容</p>
</div>
</div>
)
console.log(elementObj);
return elementObj;
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
JSX代码
ReactElement对象
真实DOM
为什么使用虚拟DOM
为什么采用虚拟DOM,而不是直接修改真实DOM
- 很难追踪状态发生的变化:原有的开发模式,很难追踪到状态发生的变化,不方便针对我们应用程序进行调试
- 操作真实DOM性能较低:传统的开发模式,会频繁的对DOM进行操作,而这种做法的性能非常的低
DOM操作性能低:
- document.createElement本身创建出来的是一个非常复杂的对象https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement
- DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作
频繁操作DOM的问题
比如现在有一组数组需要渲染:[0,1,2,3,4],我们可以通过ul和li将他们展示出来
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
后来我们又增加了5条数据:[0,1,2,3,4,5,6,7,8,9]
方式一:重新遍历整个数组
方式二:在ul后面追加另外5个li
for (var i=5; i<10; i++){
var li = document.createElement("li")
li.innerHTML = arr[i]
ul.appendChild(li)
}
这段代码的性能非常低效
因为我们通过document.createElement创建元素,再通过ul.appendChild(li)渲染到DOM上,进行了多次DOM操作
对于批量操作,最好的办法不是一次次修改DOM,而是对批量的操作机械能合并,比如可以通过DocumentFragment进行合并
我们可以通过Virtual DOM来帮助我们解决上面的问题
声明式编程
虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
React官方的说法:Virtual DOM是一种编程理念
- 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
- 我们可以通过ReactDOM.render让虚拟DOM和真实DOM同步起来,这个过程叫做协调(Reconciliation)
这种编程方式赋予了React声明式的API
- 你只需要告诉React希望UI是什么状态
- React来确保DOM和这些状态是匹配的
- 你不需要直接进行DOM操作,可以从手动更改DOM、属性操作、事件处理中解放出来
案例练习
需求
- 在界面以表格的形式,显示一些数据的数据
- 在底部显示书籍的总加个
- 点击+或者-可以减少书籍数量,如果数量为1,则不能继续-
- 点击移除按钮,可以将数据移除,当所有书籍都被移除完毕时,显示:购物车为空
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
table {
border: 1px solid #eee;
border-collapse: collapse;
}
th, td {
border: 1px solid #eee;
padding: 10px 16px;
text-align: center;
}
th {
background-color: #ccc;
}
.count {
margin: 0 5px;
}
</style>
<script src="format-util.js"></script>
</head>
<body>
<div id="app"></div>
<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script src="./format-util.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
books: [
{
id: 1,
name: '《算法导论》',
date: '2006-9',
price: 85.00,
count: 1
},
{
id: 2,
name: '《UNIX编程艺术》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《编程珠玑》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
price: 128.00,
count: 1
},
]
}
}
renderBooks() {
return (
<div>
<table>
<thead>
<tr>
<th></th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
this.state.books.map((item, index) => {
return (
<tr>
<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>
<span className="count">{item.count}</span>
<button onClick={() => this.changeCount(index, 1)}>+</button>
</td>
<td>
<button onClick={() => this.remove(index)}>移除</button>
</td>
</tr>
)
})
}
</tbody>
</table>
<span>总价格:{this.getTotalPrice()}</span>
</div>
)
}
renderEmpty() {
return (
<h2>购物车为空</h2>
)
}
render() {
return this.state.books.length ? this.renderBooks() : this.renderEmpty()
}
changeCount(index, num) {
const newBooks = [...this.state.books]
newBooks[index].count += num
this.setState({
books: newBooks
})
}
remove(index) {
this.setState({
books: this.state.books.filter((item, idx) => index != idx)
})
}
getTotalPrice() {
let totalPrice = this.state.books.reduce((previousValue, item) => {
return previousValue + item.price * item.count
}, 0);
return formatPrice(totalPrice);
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
</script>
</body>
</html>
function formatPrice(price){
if (typeof price != 'number'){
price = Number(price) || 0
}
return "¥" + price.toFixed(2)
}