在ES6之前,我们通过function来定义类,但是这种模式一直被很多从其他编程语言(比如Java、C++、OC等等)转到JavaScript的人所不适应。原因是,大多数面向对象的语言,都是使用class关键字来定义类的。而JavaScript也从ES6开始引入了class关键字,用于定义一个类。ES6之前定义一个Person类:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.running = function() {
console.log(this.name + this.age + "running");
}
var p = new Person("why", 18);
p.running();
转换成ES6中的类如何定义呢?类中有一个constructor构造方法,当我们通过new关键字调用时,就会默认执行这个构造方法。构造方法中可以给当前对象添加属性,类中也可以定义其他方法,这些方法会被放到Person类的prototype上。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
running() {
console.log(this.name + this.age + "running");
}
}
const p = new Person("why", 18);
p.running();
继承是面向对象的一大特性,可以减少我们重复代码的编写,方便公共内容的抽取(也是很多面向对象语言中,多态的前提)。ES6中增加了extends关键字来作为类的继承。我们先写两个类没有继承的情况下,它们存在的重复代码:Person类和Student类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
running() {
console.log(this.name, this.age, "running");
}
}
class Student {
constructor(name, age, sno, score) {
this.name = name;
this.age = age;
this.sno = sno;
this.score = score;
}
running() {
console.log(this.name, this.age, "running");
}
studying() {
console.log(this.name, this.age, this.sno, this.score, "studing");
}
}
我们可以使用继承来简化代码:注意:在constructor中,子类必须通过super来调用父类的构造方法,对父类进行初始化,否则会报错。
class Student1 extends Person {
constructor(name, age, sno, score) {
super(name, age);
this.sno = sno;
this.score = score;
}
studying() {
console.log(this.name, this.age, this.sno, this.score, "studing");
}
}
const stu1 = new Student1("why", 18, 110, 100);
stu1.studying();
<!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="app"></div>
<!-- 引入依赖 -->
<script src="./react/react.development.js"></script>
<script src="./react/react-dom.development.js"></script>
<script src="./react/babel.min.js"></script>
<!-- 编写 react 代码 -->
<script type="text/babel">
class App extends React.Component {
constructor () {
super();
this.state = {
message: 'Hello World',
movies: ['大话西游', '盗梦空间', '星际穿越', '流浪地球']
}
}
render () {
const liArr = [];
for (let i of this.state.movies) {
liArr.push(<li>{i}</li>)
}
return (
<div>
<h2>电影列表</h2>
<ul>
{liArr}
</ul>
<h2>电影列表2</h2>
<ul>
{this.state.movies.map(i => (<li>{i}</li>))}
</ul>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('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="app"></div>
<!-- 引入依赖 -->
<script src="./react/react.development.js"></script>
<script src="./react/react-dom.development.js"></script>
<script src="./react/babel.min.js"></script>
<!-- 编写 react 代码 -->
<script type="text/babel">
class App extends React.Component {
constructor () {
super();
this.state = {
count: 0
}
}
increase () {
console.log('+1')
this.setState({
count: this.state.count + 1
})
}
decrease () {
console.log('-1')
this.setState({
count: this.state.count - 1
})
}
render () {
return (
<div>
<h2>当前计数:{this.state.count}</h2>
<button onClick={this.increase.bind(this)}>+1</button>
<button onClick={this.decrease.bind(this)}>-1</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
</script>
</body>
</html>
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中的内容是动态的,我们可以通过表达式来获取:大括号内可以是变量、字符串、数组、函数调用等任意js表达式;
<div>{this.state.data}</div>
jsx是嵌入到JavaScript中的一种语法,所以在编写注释时,需要通过JSX的语法来编写:
{/* 这是一段注释 */}
- 情况一:当变量是Number、String、Array类型时,可以直接显示
- 情况二:当变量是null、undefined、Boolean类型时,内容为空;如果希望可以显示null、undefined、Boolean,那么需要转成字符串;转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;
- 情况三:对象类型不能作为子元素(not valid as a React child),如果放到花括号中放入 jsx 中直接会报错。
- 补充:为什么null、undefined、Boolean在JSX中要显示为空内容呢?原因是在开发中,我们会进行很多的判断;在判断结果为false时,不显示一个内容;在判断结果为true时,显示一个内容;
class App extends React.Component {
constructor () {
super();
this.state = {
data1: 6,
data2: 'Hello World',
data3: [123, 456, 789],
data4: undefined,
data5: null,
data6: true,
data7: false,
obj: {
name: 'tom',
age: 28
}
}
}
render () {
const { data1, data2, data3, data4, data5, data6, data7} = this.state;
return (
<div>
{/* 可以正常显示的类型 */}
<div>{data1}</div>
<div>{data2}</div>
<div>{data3}</div>
<br />
{/* 不会显示的类型 */}
<div>{data4}</div>
<div>{data5}</div>
<div>{data6}</div>
<br />
{/* 对象直接放入大括号中会报错 */}
<div>{this.state.obj}</div>
{/* Objects are not valid as a React child */}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
- 运算表达式
- 三元运算符
- 执行一个函数
class App extends React.Component {
constructor () {
super();
this.state = {
info: {
name: 'tom',
age: '28'
},
isLogin: true
}
}
sum (a, b) {
return a + b;
}
render () {
const { info, have, isLogin } = this.state;
return (
<div>
{/* 表达式 */}
<div>{info.name + ' ' + info.age}</div>
<div>{20 + 100}</div>
{/* 三元运算符 */}
<div>{isLogin ? '已登录' : '请登录'}</div>
{/* 函数调用 */}
<div>{this.sum(1, 6)}</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
- 比如元素都会有title属性
- 比如img元素会有src属性
- 比如a元素会有href属性
- 比如元素可能需要绑定class
- 比如原生使用内联样式style
class App extends React.Component {
constructor () {
super();
this.state = {
title: '标题',
imgUrl: 'http://p1.music.126.net/z3mxHzSvXoMPhWYYPWAstA==/109951166006554807.jpg',
link: 'http://www.baidu.com',
active: true
}
}
getSizeImg (img, size) {
return `${img}?param=${size}x${size}`
}
render () {
const { title, imgUrl, link, active } = this.state;
return (
<div>
{/* 普通属性 */}
<h2 title={title}>我是标题</h2>
<img src={this.getSizeImg(imgUrl, 140)} alt="" />
<a href={link}>百度</a>
{/* 绑定class */}
<div className="box title">className</div>
<div className={'box title' + (active ? 'active' : '')}>className</div>
<label htmlFor=""></label>
{/* 绑定style */}
<div style={{color: 'blue', fontSize: '20px'}}>绑定style</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
我们来实现一下React中的事件监听,这里主要有两点不同
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
- 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行;
在事件执行后,我们可能需要获取当前类的对象中相关的属性,这个时候需要用到this如果我们这里直接打印this,也会发现它是一个undefined。为什么是undefined呢?原因是btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数;而它内部调用时,并不知道要如何绑定正确的this;如何解决this的问题呢?
- 方案一:bind给btnClick显示绑定this
- 方案二:使用ES6 class fields 语法
- 方案三:事件监听时传入箭头函数(推荐)
class App extends React.Component {
constructor () {
super();
this.state = {
message: '你好',
count: 0
}
this.btnClick = this.btnClick.bind(this);
}
btnClick () {
console.log(this.state.message, '点击了')
}
increase = () => {
this.setState({
count: this.state.count + 1
});
}
decrease () {
this.setState({
count: this.state.count - 1
})
}
render () {
const { message, count } = this.state;
return (
<div>
<div>{message}</div>
<div>{count}</div>
{/* 显示绑定 */}
<button onClick={this.btnClick.bind(this)}>按钮</button>
{/* 定义函数时使用箭头函数 */}
<button onClick={this.increase}>+1</button>
{/* 给事件绑定传递一个箭头函数,在箭头函数中调用要执行的函数 */}
<button onClick={e => {this.decrease()}}>-1</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
在执行事件函数时,有可能我们需要获取一些参数信息:比如event对象、其他参数
- 情况一:获取event对象
- 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
- 假如我们用不到this,那么直接传入函数就可以获取到event对象;
- 情况二:获取更多参数
- 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;
class App extends React.Component {
constructor () {
super();
this.state = {
movies: ['盗梦空间', '流浪地球', '大话西游']
}
}
liClick (item, index, event) {
console.log(item, index, event)
}
btnClick (e) {
console.log(e)
}
render () {
const { movies } = this.state;
return (
<div>
<button onClick={this.btnClick}>按钮</button>
<ul>
{
movies.map((item, index) => (<li onClick={(e) => {this.liClick(item, index, e)}}>{item}</li>))
}
</ul>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
常见的条件渲染的方式有哪些呢?
- 方式一:条件判断语句 适合逻辑较多的情况
- 方式二:三元运算符 适合逻辑比较简单
- 与运算符&& 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染;
- v-show的效果 主要是控制display属性是否为none
class App extends React.Component {
constructor () {
super();
this.state = {
isLogin: false
}
}
btnClick () {
this.setState({
isLogin: !this.state.isLogin
})
}
render () {
const { isLogin } = this.state;
let welcome;
{/* 条件判断语句 */}
if (isLogin) {
welcome = '欢迎回来'
} else {
welcome = '请先登录'
}
return (
<div>
{welcome}
{/* 三目运算符 */}
<button onClick={() => {this.btnClick()}}>{isLogin ? '退出' : '登录'}</button>
<hr />
{/* 逻辑与运算符 */}
<div>{isLogin && '你好!'}</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
实现 v-show 的效果:
class App extends React.Component {
constructor () {
super();
this.state = {
isLogin: false
}
}
btnClick () {
this.setState({
isLogin: !this.state.isLogin
})
}
render () {
const { isLogin } = this.state;
return (
<div>
<button onClick={() => {this.btnClick()}}>{isLogin ? '退出' : '登录'}</button>
<h2 style={{display: isLogin ? 'block' : 'none'}}>你好!</h2>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
如何展示列表呢?
- 在React中,展示列表最多的方式就是使用数组的map高阶函数;
- 很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:
- 比如过滤掉一些内容:filter函数
- 比如截取数组中的一部分内容:slice函数
class App extends React.Component {
constructor () {
super();
this.state = {
name: ['cba', 'abc', 'nba', 'dna'],
number: [110, 123, 50, 23, 32, 55, 10, 8, 333]
}
}
render () {
const { name, number } = this.state
return (
<div>
<ul>
{
name.map(item => (<li>{item}</li>))
}
</ul>
<hr />
<ul>
{
number.filter(item => item >= 50).map(item => (<li>{item}</li>))
}
</ul>
<hr />
<ul>
{
number.slice(0, 4).map(item => (<li>{item}</li>))
}
</ul>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
实际上,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数组的方式进行存储;
- 参数一:type 当前ReactElement的类型;
-
我们知道默认jsx是通过babel帮我们进行语法转换的,所以我们之前写的jsx代码都需要依赖babel。
-
可以在babel的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react
<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>
经过 babel 转换后:
"use strict";
/*#__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")));
我们通过React.createElement 最终创建出来一个ReactElement对象:这个ReactElement对象是什么作用呢?React为什么要创建它呢?原因是React利用ReactElement对象组成了一个JavaScript的对象树;JavaScript的对象树就是大名鼎鼎的虚拟DOM(Virtual DOM);如何查看ReactElement的树结构呢?我们可以将之前的jsx返回结果进行打印;注意下面代码中我打jsx的打印;而ReactElement最终形成的树结构就是Virtual DOM;
{
$$typeof: Symbol(react.element),
key: null,
props: {children: Array(3)},
ref: null,
type: "div",
_owner: FiberNode {tag: 1, key: null, stateNode: App, elementType: ƒ, type: ƒ, …},
_store: {validated: false},
}
为什么要采用虚拟DOM,而不是直接修改真实的DOM呢?
- 很难跟踪状态发生的改变:原有的开发模式,我们很难跟踪到状态发生的改变,不方便针对我们应用程序进行调试;
- 操作真实DOM性能较低:传统的开发模式会进行频繁的DOM操作,而这一的做法性能非常的低;首先,document.createElement本身创建出来的就是一个非常复杂的对象;https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement。其次,DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作;
虚拟DOM帮助我们从命令式编程转到了声明式编程的模式。React官方的说法:Virtual DOM 是一种编程理念。在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象。我们可以通过ReactDOM.render让虚拟DOM 和真实DOM同步起来,这个过程中叫做协调(Reconciliation);这种编程的方式赋予了React声明式的API:你只需要告诉React希望让UI是什么状态;React来确保DOM和这些状态是匹配的;你不需要直接进行DOM操作,只可以从手动更改DOM、属性操作、事件处理中解放出来;