1. React
1.1. 常识
React 是什么:用于构件用户界面的 JavaScript 库(将数据渲染为 HTML 视图,它不关注获取数据啥的,只给你渲染到 HTML 上面)
为什么要学:
- 原生 JavaScript 操作 DOM (jQuery 也是操作 DOM,只是写起来简单) 繁琐、效率低(比如 document.getElementById()
一句话就是:DOM-API 操作 UI
- 直接操作 DOM,浏览器会进行
大量的重新绘制、重新排列
,造成效率低下 - 原生 JavaScript 没有
组件化
编码方案,代码复用率低
React 优点:
- 采用
组件化模式、声明式编码
(命令式和声明式就像手动挡与自动挡的区别,命令式的步骤不能缺少,才能完成一个任务;而声明式可以自动帮你完成) - 在 React Native 中可以使用 React 语法进行
移动端开发
(利用 JavaScript 开发 app) - 使用
虚拟 DOM + Diffing 算法
,尽可能地减少了与真实 DOM 交互
1.2. 旧版本使用
1.2.1. 库简介
-
babel.js
- 之前的作用有:
es6 转 es5 语法,让浏览器能够识别;模块化的时候 -> import 也需要靠它进行工作
- 现在的新的一个作用:
将 jsx 转为 js
- 之前的作用有:
-
react.development.js
- react 的核心库
-
react-dom.development.js
- react 用于操作 DOM 的核心库
1.2.2. 编写 hello-world
<body>
<!-- 1.准备好容器 -->
<div id="root"></div>
<!-- 2.引入核心库 -->
<script src="js/react.development.js"></script> <!-- 必须要先引入 react 核心库 -->
<script src="js/react-dom.development.js"></script>
<script src="js/babel.min.js"></script>
<!-- 必须写成 babel,意思是里面的代码是 jsx,需要用 babel 来转换 -->
<script type="text/babel">
// 3.创建虚拟 DOM
const VDOM = <h1>Hello React</h1> // 这里不是字符串,这就是个虚拟 DOM
// 4.渲染虚拟 DOM 到 HTML 上
ReactDOM.render(VDOM, document.getElementById('root')) // 引入 react 就会在 window 添加一个 React 成员;引入 react-dom 就会在 window 添加一个 ReactDOM 成员
console.log(window)
</script>
</body>
1.2.3. 为什么要用 jsx
需求:生成 <div id="root"><span>Hello React</span></div>
这样一个节点
原生 JavaScript:
// createElement 是创建真实 DOM
React.createElement('div', {
id: 'root' }, React.createElement('span', {
}, 'Hello World'))
jsx:
const VDOM = (
<div>
<span>Hello React</span>
</div>
)
总结:这里就可以看出来 jsx 的优势了,它能够很轻松地创建虚拟 DOM (但实际上,它还是会翻译成上面的结果,只是我们写起来方便,就是个语法糖而已)
1.2.4. VDOM 是什么
- 虚拟 DOM 实际上是 Object
- 虚拟 DOM 比较
轻
,它只需要 react 内部使用,就不需要那么多真实 DOM 的属性 - 虚拟 DOM 最终会被渲染成真实 DOM,放在页面上
1.2.5. jsx(JavaScript XML)
如下是 JSX 的一些语法规则:
- 定义虚拟 DOM 时,不能有引号
- 标签内有
表达式
时,得用{ username }
- 样式要用
className = "username"
- 内联样式用
{ { color: 'red' }}
,可以看成 {} 传入了一个对象 { color: ‘#f00’ } - 只能有一个
根标签
,如果要写多个兄弟节点,就只能在外层包一层 - 标签
必须闭合,否则报错
,如:<input type="text"/>
- 尽量别乱写标签,如:
<good>123</good>
,小写开头的标签会自动转换成 HTML 元素,控制台会报错;大写开头就是组件
,如果没有定义的话,也会报错// 例子 const id = 'username' const username = 'yuwan' const VDOM = ( <h1 id={id} className="username">{ username.toLowerCase() } <span style={ { color: '#f00', fontSize: '30px' }}></span> </h1> )
Tip:
表达式
和代码
的区别:
1.表达式返回一个值,比如:username、a+b、add(1, 3)【左侧写一个变量,能够接收到的就是表达式】
2.比如这些就是代码而不是表达式:if() 、{}、 for(){}、 switch(){}等
1.2.6. 小案例 - 动态生成前端框架名称
const title = '前端 js 框架列表'
const frameworks = ['Angular.js', 'React.js', 'Vue.js']
const VDOM = (
<div>
<h1>{
title}</h1>
<ul>
{
// 这里是不能写 forEach 来遍历,因为它没有返回值;写 map 才能动态生成出来
frameworks.map((item, index) => {
return <li key={
index}>{
item}</li>
})
}
</ul>
<p>
{
}
</p>
</div>
)
ReactDOM.render(VDOM, document.getElementById('root'))
1.2.7. 模块、组件、模块化、组件化的理解
1.2.7.1. 模块
- 理解:一般就是一个 js 文件,就是一个模块
- 为什么分模块:按照业务逻辑增加,代码会增多且复杂
- 作用:复用 js,简化 js 的编写,提高 js 运行效率
1.2.7.2. 组件
- 理解:用来实现一些功能的
代码和资源
的集合(每个组件有自己的 HTML、CSS、JS、Video、image,简言之就是什么都拆了) - 为什么分组件:一个界面的功能更加复杂的时候,分组件就能更好地复用代码
- 作用:复用代码,简化项目编码,提高运行效率
1.2.7.3. 模块化
当应用的 js 都是用模块来编写的,那么这个应用就是一个模块化的应用
1.2.7.4. 组件化
当应用都是以组件方式来进行开发,那么这个应用就是一个组件化的应用
1.3. react 面向组件编程
1.3.1. 定义组件
react 中,组件有两种:函数式组件
、类式组件
1.3.1.1. 函数式组件
粘贴 CreateApp 代码,在线查看 babel 转换结果
渲染过程:
- 1.React 解析组件标签,找到 CreateApp 组件(若找不到则会报错)
- 2.发现 CreateApp 是使用函数定义的,因此就会调用该函数,将返回的虚拟 DOM 渲染为真实 DOM,随后呈现在页面中
注意:
- babel 在转换过程中开启了严格模式,导致 this 不能指向 window,因此是 undefined
<script type="text/babel">
function CreateApp() {
console.log(this) // undefined
return <h1>我是函数式组件,我适合于简单组件的创建</h1>
}
ReactDOM.render(<CreateApp/>, document.getElementById('app'))
</script>
这里 render(CreateApp(), …) 也会呈现出内容,但是它不是个组件了,在 Console -> Components 下无法找到
1.3.1.2. 类式组件
类知识复习
<script>
class Person {
constructor(name, age) {
// 构造器中的 this 指向的是实例对象,new Person() 的谁就指向谁
this.name = name // 构造器是可以不写的,因为继承
this.age = age
}
speak() {
// 一般方法:speak 放在了哪里? 原型对象上,供所有实例都能够调用
// 这里 this 指向函数调用的对象。可能是 Person 实例对象,也可能是 call、bind、apple 等改变 this 指向的对象
console.log(`我叫${
this.name},我今年 ${
this.age}岁啦!`)
}
}
const p1 = new Person('zhang', 18)
console.log(p1)
p1.speak()
p1.speak.call({
name:'yuwan', age:20})
console.log('----------华丽的分割线-----------')
class Student extends Person{
constructor(name, age, grade) {
super(name, age); // 一旦 Student 继承 Person 而且写了 constructor,必须调用 super() 而且是第一句调用
this.grade = grade
}
speak() {
console.log(`我叫${
this.name},我今年 ${
this.age}岁啦,我读${
this.grade}年级`)
}
}
const s1 = new Student('yuwan', 22, '研究生')
console.log(s1)
s1.speak() // 根据原型链的查找方式,会先找 Student 原型对象 - Person 的 speak 方法,从而实现覆盖 Person 原型对象的 speak 方法
</script>
总结:
-
constructor 可写可不写,具体看要求
-
class Son extends Father,且 Son 写了 constructor,就必须调用 super()【可以简化编码】
-
类中定义的一般方法,放在了类的原型对象上,供所有实例使用
-
注意 this 的指向【构造器中,this 指向的是 new Person();一般方法的 this就要看具体情况了】
-
注意原型链查找方式
渲染过程:
- 1.react 解析标签,找到 MyApp 组件
- 2.发现这个组件是类定义的,就会 new MyApp() 然后调用了 render 方法
- 3.将 render() 返回的虚拟 DOM 渲染到页面的真实 DOM 中
<script type="text/babel">
class MyApp extends React.Component {
render() {
// 这个方法在 MyApp 的原型对象上,供所有 MyApp 实例对象调用
return <h1>我是类式组件,适用于较复杂的组件</h1>
}
}
ReactDOM.render(<MyApp/>, document.getElementById('app'))
</script>
1.3.2. 组件核心属性之 state
理解:简单理解就是组件自身所需要的数据
;组件被称为状态机
,通过更新组件的 state 来更新对应的页面显示(重新渲染组件)
注意事项:
- 组件中 render 的 this 指向了组件实例
- 组件中自定义方法中 this 为 undefined,怎么解决?(bind、()=>{})
- 状态中的数据不可直接修改,通过
setState
修改
<script>
class MyComponent extends React.Component{
constructor(props) {
super(props); // 按照上面所说,有继承且写了 constructor 就必须要调用 super
this.state = {
isHot: true } // 初始化 state。当前组件的 state 是一个对象,便于保存多种类型的数据
}
render() {
const {
isHot } = this.state
return <h1>今天天气很{
isHot? '炎热':'凉爽'}</h1>
}
}
ReactDOM.render(<MyComponent/>, document.getElementById('app'))
</script>
1.3.2.1. 点击事件
原生 JavaScript 的点击事件分为三种写法,React 推荐第三种写法
const btn1 = document.getElementById('btn1')
btn1.addEventListener('click',() => {
alert('hello world')
})
const btn2 = document.getElementById('btn2')
btn2.onclick = () => {
alert('hello world')
}
// <button οnclick="test()">按钮3</button>
function test() {
alert('hello world')
}
1.3.2.2. 类中方法 this 指向
类中的函数默认开启了严格模式
,导致直接通过 varSpeak() 调用时,会输出 undefined
<script>
class Person {
constructor(name,age) {
this.name = name
this.age = age
}
speak() {
// 默认是开启了局部严格模式
console.log(this)
}
}
const p1 = new Person('tom', 19)
p1.speak() // Person {name: 'tom', age: 19}
const varSpeak = p1.speak
varSpeak() // undefined
</script>
1.3.2.3. React 绑定事件
如下是演示,我们在不知道 react 怎么绑定事件的过程:首先想到的是传函数调用结果 -> 传表达式调用结果 -> 传函数名 -> 解决 this -> this.xxx 调用
<script>
class MyApp extends React.Component{
// ...
render() {
return <h1 onClick="test()"></h1>
}
}
function test(){
console.log('this is test message.')
}
// ERROR: Expected `onClick` listener to be a function, instead got a value of `string` type.
// 1.本来点击事件要回调函数,但是这样写就是传给它一个 string,因此报错
return <h1 onClick={
test() }></h1>
// ERROR: 控制台直接输出:this is test message.
// 2.这相当于把 test() 的返回值传给了 onClick 作为回调函数,而 test() 执行就会打印输出,进而返回 undefined,因此此时点击是无响应的
return <h1 onClick={
test }></h1>
// 3.这里点击就可以输出: this is test message.
// 由于函数写在类外部,我们获取不到 MyApp 实例对象,因此在 test 方法中无法操作 MyApp 实例对象的 state
class MyApp extends React.Component{
// ...
render() {
return <h1 onClick={
test }></h1>
}
test(){
console.log(this)
console.log('this is test message.')
}
}
// ERROR: test is not defined
// 4.因为在一个类中的方法体里,调用类的其他方法,肯定得要用 this.xx 才能调用
return <h1 onClick={
this.test }></h1>
// 点击 h1,执行 test() 输出 undefined
// 正如前面一节,类中 this 指向,相当于 onClick = this.test; 在某个时刻,onClick() 这样调用了,而类中函数开启了严格模式,因此输出 undefined
</script>
绑定事件的正确用法
有两种方式:bind 来改变 this
、利用箭头函数的特点
<script>
class App extends React