refactor: 目录结构调整
This commit is contained in:
302
12-React基础/01-React介绍.md
Normal file
302
12-React基础/01-React介绍.md
Normal file
@@ -0,0 +1,302 @@
|
||||
|
||||
|
||||
## 虚拟DOM和diff算法
|
||||
|
||||
> 在学习 React 之前,我们需要先了解两个概念:虚拟DOM、diff算法。
|
||||
|
||||
|
||||
### 虚拟DOM
|
||||
|
||||
**问题描述**:
|
||||
|
||||
假设我们的数据发生一点点的变化,也会被强制重建整颗DOM树,这么做,会涉及到很多元素的重绘和重排,导致性能浪费严重。
|
||||
|
||||
**解决上述问题的思路**:
|
||||
|
||||
实现按需更新页面上的元素即可。也就是说,把 需要修改的元素,所对应的 DOM 元素重新构建;其他没有变化的数据,所对应的 DOM 节点不需要被强制更新。
|
||||
|
||||
**具体实现方案**:(如何按需更新页面上的元素)
|
||||
|
||||
只需要拿到 页面更新前的 内存中的DOM树,同时再拿到 页面更新前的 新渲染出来的 内存DOM树;然后,对比这两颗新旧DOM树,找到那些需要被重新创建和修改的元素即可。这样就能实现 DOM 的**按需更新**。
|
||||
|
||||
|
||||
**如何拿到这两棵DOM树**:(即:如何从浏览器的内存住哪个获取到 浏览器私有的那两颗DOM树?)
|
||||
|
||||
如果要拿到浏览器私有的DOM树,那我们必须调用浏览器提供的相关JS的API才行。但是问题来了,浏览器并没有提供这样的API。既然如此,那我们可以自己**模拟**这两颗 新旧DOM树。
|
||||
|
||||
**如何自己模拟这两颗 新旧DOM树**:(即:如何自己模拟一个DOM节点?)
|
||||
|
||||
这里涉及到手动模拟DOM树的原理:使用 JS 创建一个对象,用和这个对象来模拟每一个DOM节点;然后在每个DOM节点中,又提供了类似于 children 这样的属性来描述当前DOM的子节点。这样的话,当DOM节点形成了嵌套关系,就模拟出了一颗 DOM 树。
|
||||
|
||||
|
||||
**总结**:
|
||||
|
||||
- 虚拟DOM的**本质**:使用 JS 对象模拟DOM树。
|
||||
|
||||
- 虚拟DOM的**目的**:为了实现 DOM 节点的高效更新。
|
||||
|
||||
React内部已经帮我们实现了虚拟DOM,初学者掌握如何调用即可。
|
||||
|
||||
|
||||
### diff算法
|
||||
|
||||
怎么实现 两颗新旧DOM树的对比 呢?这里就涉及到了 diff算法。常见的 diff算法如下:
|
||||
|
||||
- tree diff:新旧DOM树,逐层对比的方式,就叫做 tree diff。每当我们从前到后,把所有层的节点对比完后,必然能够找到那些 需要被更新的元素。
|
||||
|
||||
- component diff:在对比每一层的时候,组件之间的对比,叫做 component diff。当对比组件的时候,如果两个组件的类型相同,则暂时认为这个组件不需要被更新,如果组件的类型不同,则立即将旧组件移除,新建一个组件,替换到被移除的位置。
|
||||
|
||||
- element diff:在组件中,每个元素之间也要进行对比,那么,元素级别的对比,叫做 element diff。
|
||||
|
||||
- key:key这个属性,可以把 页面上的 DOM节点 和 虚拟DOM中的对象,做一层关联关系。
|
||||
|
||||
|
||||
## React 介绍
|
||||
|
||||
### React 是什么
|
||||
|
||||
- Facebook 开源的一个JS库。
|
||||
|
||||
- 一个用于动态构建用户界面的JS库。
|
||||
|
||||
### React 的特点
|
||||
|
||||
- Declarative(声明式编码)
|
||||
|
||||
- Component-Based(组件化编码)
|
||||
|
||||
- Learn Once, Write Anywhere(支持客户端、服务器端渲染)
|
||||
|
||||
- 高效的DOM Diff算法,最小化页面重绘
|
||||
|
||||
- 单向数据流
|
||||
|
||||
### React高效的原因
|
||||
|
||||
- 虚拟(virtual)DOM,不总是直接操作DOM
|
||||
|
||||
- 高效的DOM Diff算法,最小化页面重绘(即“局部渲染”)。
|
||||
|
||||
虚拟DOM指的是:在真实DOM的上一层**映射**一层虚拟DOM。我们操作的是映射关系,而不是真实的DOM。假设页面的样式做了修改(比如新增了一个标签),此时修改的是虚拟DOM的样式,真实的DOM并未发生变化。那什么时候,真实的DOM会发生变化呢? 当我把所有的内容操作完之后,转化为真实的DOM,此时要打包统一的渲染页面,于是真实的DOM发生变化,然后渲染一次。 这样做的话,可以减少页面的渲染次数。
|
||||
|
||||
### 相关网址
|
||||
|
||||
- 官网:<https://reactjs.org/>
|
||||
|
||||
- GitHub 地址:<https://github.com/facebook/react> 截至2019-02-08,React项目已经有 121k 的star。
|
||||
|
||||
官网截图:
|
||||
|
||||
20190208_1057.png
|
||||
|
||||
上方截图中,有一个特性是“Learn Once, Write Anywhere”。这里的 “Anywhere” 其实指的是两个地方:一个是浏览器端,一个是服务器端。后者指的是,**React支持在服务器端渲染页面**。
|
||||
|
||||
### 生态介绍
|
||||
|
||||
- Vue生态:Vue + Vue-Router + Vuex + Axios + Babel + Webpack
|
||||
|
||||
- React生态:React + React-Router + Redux + Axios + Babel + Webpack
|
||||
|
||||
|
||||
## React 模块化、组件化
|
||||
|
||||
### 模块
|
||||
|
||||
- 理解:向外提供特定功能的js程序, 一般就是一个js文件
|
||||
|
||||
- 理由:js代码更多更复杂
|
||||
|
||||
- 作用:简化js的编写,阅读,提高运行效率
|
||||
|
||||
### 组件
|
||||
|
||||
- 理解:用来实现特定功能效果的代码集合(html/css/js)
|
||||
|
||||
- 理由:一个界面的功能更复杂
|
||||
|
||||
- 作用:复用,简化项目编码,提高运行效率
|
||||
|
||||
### 模块化与组件化
|
||||
|
||||
- 模块化:当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
|
||||
|
||||
- 组件化:当应用是以多组件的方式实现功能, 这上应用就是一个组件化的应用
|
||||
|
||||
|
||||
### 面相对象与面向过程的区别
|
||||
|
||||
面向对象编程:
|
||||
|
||||
- 重点是对象
|
||||
|
||||
- 更加关心的是干活的人
|
||||
|
||||
面向过程编程:
|
||||
|
||||
- 更加关心的是干活的过程
|
||||
|
||||
- 谁去干活儿不关心
|
||||
|
||||
|
||||
## React 环境搭建:写第一个Hello World
|
||||
|
||||
### react.js 和 react-dom.js
|
||||
|
||||
为了通过 React 写一个Hello World程序,我们需要先安装几个包:
|
||||
|
||||
- react.js: React的核心库。这个包,是专门用来创建React组件、组件生命周期等。
|
||||
|
||||
- react-dom.js: 操作DOM的扩展库。这个包,主要封装了和 DOM 操作相关的包(比如,把组件渲染到页面上)。
|
||||
|
||||
- babel.min.js: 将 JSX语法 解析为 纯JS语法代码。
|
||||
|
||||
### 方式一:本地引入相关的js库
|
||||
|
||||
入门的时候,我们建议采取方式一。
|
||||
|
||||
如果是本地引入的话,可以这样写:
|
||||
|
||||
```html
|
||||
<!-- 引入React相关的js库 -->
|
||||
<script type="text/javascript" src="./libs/react.js"></script>
|
||||
<script type="text/javascript" src="./libs/react-dom.js"></script>
|
||||
<script type="text/javascript" src="./libs/babel.min.js"></script>
|
||||
|
||||
```
|
||||
|
||||
如果是通过CDN的方式引入的话,可以使用网站 <https://www.bootcdn.cn/> 提供的CDN链接。
|
||||
|
||||
**完整代码举例**:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta />
|
||||
<meta />
|
||||
<meta />
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 引入React相关的js库 -->
|
||||
<script type="text/javascript" src="./libs/react.js"></script>
|
||||
<script type="text/javascript" src="./libs/react-dom.js"></script>
|
||||
<script type="text/javascript" src="./libs/babel.min.js"></script>
|
||||
|
||||
<div id="myContainer"></div>
|
||||
|
||||
<!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
|
||||
<script type="text/babel">
|
||||
|
||||
//页面中的真实容器元素
|
||||
var containDiv = document.getElementById("myContainer");
|
||||
|
||||
//1、创建虚拟DOM对象
|
||||
var vDom = <div>Hello, React!</div>; // 不是字符串, 不能加引号
|
||||
|
||||
//2、渲染虚拟DOM对象(将虚拟DOM对象渲染到页面元素中)
|
||||
ReactDOM.render(vDom, containDiv); // 参数1:虚拟DOM对象;参数2:页面中的容器
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
代码运行后,页面上的DOM结构如下:
|
||||
|
||||
```html
|
||||
<div id="myContainer">
|
||||
<div>Hello, React!</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
**代码解释**:
|
||||
|
||||
render的中文含义是“渲染”。render 方法的语法如下:
|
||||
|
||||
```javascript
|
||||
ReactDOM.render(要渲染的虚拟DOM对象, 容器 container:要渲染到页面上的哪个位置);
|
||||
```
|
||||
|
||||
【工程文件下载】
|
||||
|
||||
- [2019-02-08-ReactDemo.zip](https://download.csdn.net/download/smyhvae/10951736)
|
||||
|
||||
|
||||
### 方式二:npm install
|
||||
|
||||
实际开发中,我们一般都是通过 npm install 的方式来安装 react 相关的包。
|
||||
|
||||
首先,新建一个空的文件夹`2019-02-08-ReactDemo`,作为项目的根目录。然后在根目录下执行如下命令,进行**项目初始化**:
|
||||
|
||||
```
|
||||
npm init --yes
|
||||
```
|
||||
|
||||
上方命令执行完成后,会生成`package.json`文件。
|
||||
|
||||
然后继续执行如下命令,安装 react.js 和 react-dom.js 这两个包:
|
||||
|
||||
```
|
||||
npm i react react-dom
|
||||
```
|
||||
|
||||
完整代码举例:
|
||||
|
||||
index.html:
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
|
||||
main.js:
|
||||
|
||||
```javascript
|
||||
// JS打包入口文件
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
// 在 react 中,如要要创建 DOM 元素,只能使用 React 提供的 JS API 来创建,不能【直接】像 Vue 中那样,手写 HTML 元素
|
||||
// React.createElement() 方法,用于创建 虚拟DOM 对象,它接收 3个及以上的参数
|
||||
// 参数1: 是个字符串类型的参数,表示要创建的元素类型
|
||||
// 参数2: 是一个属性对象,表示 创建的这个元素上,有哪些属性
|
||||
// 参数3: 从第三个参数的位置开始,后面可以放好多的虚拟DOM对象,这写参数,表示当前元素的子节点
|
||||
|
||||
// <div title="this is a div" id="mydiv">这是一个div</div>
|
||||
var myDiv = React.createElement('div', { title: 'this is a div', id: 'mydiv' }, '这是一个div');
|
||||
|
||||
// ReactDOM.render('要渲染的虚拟DOM元素', '要渲染到页面上的哪个位置');
|
||||
ReactDOM.render(myDiv, document.getElementById('app'));
|
||||
```
|
||||
|
||||
|
||||
上方代码中,createElement()方法介绍如下:
|
||||
|
||||
```javascript
|
||||
React.createElement(需要创建的元素类型, 有哪些属性, 子节点)
|
||||
```
|
||||
|
||||
|
||||
【工程文件下载】
|
||||
|
||||
- [2019-02-09-ReactDemo.zip](https://download.csdn.net/download/smyhvae/10951196)
|
||||
|
||||
|
||||
## 我的公众号
|
||||
|
||||
想学习<font color=#0000ff>**代码之外的技能**</font>?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。
|
||||
|
||||
扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
529
12-React基础/02-JSX语法介绍.md
Normal file
529
12-React基础/02-JSX语法介绍.md
Normal file
@@ -0,0 +1,529 @@
|
||||
|
||||
## JSX介绍
|
||||
|
||||
### JSX的引入
|
||||
|
||||
如果直接让用户通过 JS 代码手动创建DOM元素,肯定是非常麻烦的。
|
||||
|
||||
于是,React 官方就提出了一套 JSX 语法规范,能够让我们在 JS 文件中,书写类似于 HTML 那样的代码,快速定义虚拟DOM结构。
|
||||
|
||||
### JSX的全称
|
||||
|
||||
JSX:JavaScript XML,一种类似于XML的JS扩展语法。也可以理解成:符合 XML 规范的 JS 语法。
|
||||
|
||||
需要注意的是,哪怕你在 JS 中写的是 JSX 语法(即JSX这样的标签),但是,JSX内部在运行的时候,并不是直接把 我们的 HTML 标签渲染到页面上;而是先把 类似于HTML 这样的标签代码,转换成 React.createElement 这样的JS代码,再渲染到页面中。
|
||||
|
||||
从这一点我们可以看出,JSX是一个对程序员友好的语法糖。
|
||||
|
||||
**JSX语法的本质**:以 React.createElement 的形式来实现的,并没有直接把 用户写的 HTML代码,渲染到页面上。
|
||||
|
||||
### babel转换工具
|
||||
|
||||
如果要直接使用 JSX 语法,需要先安装相关的 语法转换工具:
|
||||
|
||||
```
|
||||
运行 cnpm i babel-preset-react -D
|
||||
```
|
||||
|
||||
这个babel包的作用是:将 JSX语法 转换为 JS语法。
|
||||
|
||||
安装完成后,就可以开始使用JSX语法了。
|
||||
|
||||
完整代码举例:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta />
|
||||
<meta />
|
||||
<meta />
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 引入React相关的js库 -->
|
||||
<script type="text/javascript" src="./libs/react.js"></script>
|
||||
<script type="text/javascript" src="./libs/react-dom.js"></script>
|
||||
<script type="text/javascript" src="./libs/babel.min.js"></script>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
|
||||
<script type="text/babel">
|
||||
//页面中的真实容器元素
|
||||
var containDiv = document.getElementById("app");
|
||||
|
||||
//1、使用JSX语法 创建虚拟DOM对象
|
||||
var vDom = (
|
||||
<div>
|
||||
Hello, React!
|
||||
<h2>这是标题</h2>
|
||||
</div>
|
||||
);
|
||||
|
||||
//2、渲染虚拟DOM对象(将虚拟DOM对象渲染到页面元素中)
|
||||
ReactDOM.render(vDom, containDiv); // 参数1:虚拟DOM对象;参数2:页面中的容器
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
|
||||
## JSX的基本语法
|
||||
|
||||
(1)在 JSX内部 写 JS代码:如果要在 JSX 语法内部,书写 JS 代码,那么,所有的JS代码必须写到 `{}` 的内部。在{}内部,可以写任何符合JS规范的代码。
|
||||
|
||||
例如:
|
||||
|
||||
```javascript
|
||||
var myTitle = '这是使用变量定义的 tilte 值'
|
||||
|
||||
// 使用JSX语法 创建虚拟DOM对象
|
||||
var vDom = (
|
||||
<div>
|
||||
Hello, React!
|
||||
<h2 title={myTitle + 'vae'}>这是标题</h2>
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
(2)当编译引擎在编译JSX代码的时候,如果遇到了`<`,会把它当作 HTML代码 去编译;如果遇到了 `{}`, 会把方括号里面的代码当作 普通JS代码 去编译。
|
||||
|
||||
(3)在JSX中,如果要为元素添加`class`属性,则必须写成`className`,因为 `class`在ES6中是一个关键字;和`class`类似,label标签的 `for` 属性需要替换为 `htmlFor`。
|
||||
|
||||
代码举例:
|
||||
|
||||
```html
|
||||
// 使用JSX语法 创建虚拟DOM对象
|
||||
var vDom = (
|
||||
<div>
|
||||
Hello, React!
|
||||
<p className="qianguyihao">千古壹号</p>
|
||||
<label htmlFor="" />
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
(4)在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹。
|
||||
|
||||
(5)如果要写注释,注释必须放到 {} 内部。例如:
|
||||
|
||||
```javascript
|
||||
// 使用JSX语法 创建虚拟DOM对象
|
||||
var vDom = (
|
||||
// 这一行是注释
|
||||
<div>
|
||||
Hello, React!
|
||||
<p className="qianguyihao">千古壹号</p>
|
||||
{/*这一行也是注释 */}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
最后,再举个例子:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta />
|
||||
<meta />
|
||||
<meta />
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 引入React相关的js库 -->
|
||||
<script type="text/javascript" src="./libs/react.js"></script>
|
||||
<script type="text/javascript" src="./libs/react-dom.js"></script>
|
||||
<script type="text/javascript" src="./libs/babel.min.js"></script>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
|
||||
<script type="text/babel">
|
||||
//页面中的真实容器元素
|
||||
var containDiv = document.getElementById("app");
|
||||
|
||||
var arr = []
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var p = <p className="myp" key={i}>这个是p标签</p> // 注意这个地方的写法: key = {i}
|
||||
arr.push(p)
|
||||
}
|
||||
|
||||
//1、使用JSX语法 创建虚拟DOM对象
|
||||
var vDom = (
|
||||
<div>
|
||||
Hello, React!
|
||||
{arr}
|
||||
</div>
|
||||
);
|
||||
|
||||
//2、渲染虚拟DOM对象
|
||||
ReactDOM.render(vDom, containDiv); // 参数1:虚拟DOM对象;参数2:页面中的容器
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
20190210_1501.png
|
||||
|
||||
|
||||
## 创建组件的第一种方式
|
||||
|
||||
### 创建组件
|
||||
|
||||
在React中,构造函数就是一个最基本的组件。
|
||||
|
||||
如果想要把组件放到页面中,可以把**构造函数的名称**当作**组件的名称**,以 HTML标签形式引入页面中即可。
|
||||
|
||||
举例:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta />
|
||||
<meta />
|
||||
<meta />
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 引入React相关的js库 -->
|
||||
<script type="text/javascript" src="./libs/react.js"></script>
|
||||
<script type="text/javascript" src="./libs/react-dom.js"></script>
|
||||
<script type="text/javascript" src="./libs/babel.min.js"></script>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
|
||||
<script type="text/babel">
|
||||
// 这个构造函数,就相当于一个 组件
|
||||
function Hello() {
|
||||
return (
|
||||
<div>
|
||||
<h3>这是 Hello组件 中定义的元素</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<Hello> </Hello>
|
||||
</div>,
|
||||
document.getElementById("app")
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
20190210_1510.png
|
||||
|
||||
**需要注意的是**:
|
||||
|
||||
React在解析所有标签的时候,是以标签的首字母来区分的:如果标签的首字母是小写,就按照普通的 HTML 标签来解析;如果首字母是大写,则按照 **组件**的形式来解析。
|
||||
|
||||
比如上方代码中,如果把大写的 `Hello` 改成小写的 `hello`,运行会报错,无法看到预期的结果。
|
||||
|
||||
**结论**:组件的首字母必须大写。
|
||||
|
||||
### 父组件传值给子组件
|
||||
|
||||
代码举例:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta />
|
||||
<meta />
|
||||
<meta />
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 引入React相关的js库 -->
|
||||
<script type="text/javascript" src="./libs/react.js"></script>
|
||||
<script type="text/javascript" src="./libs/react-dom.js"></script>
|
||||
<script type="text/javascript" src="./libs/babel.min.js"></script>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
|
||||
<script type="text/babel">
|
||||
// 父组件中的数据
|
||||
var person = {
|
||||
name: "qianguyihao",
|
||||
age: 27,
|
||||
gender: "男",
|
||||
address: "深圳"
|
||||
};
|
||||
|
||||
// 在子组件中,如果想要使用外部传递过来的数据,必须显示的在 构造函数参数列表中,定义 props 属性来接收
|
||||
// 通过 props 得到的任何数据都是只读的,不能重新赋值
|
||||
function Hello(props) {
|
||||
return (
|
||||
<div>
|
||||
<h3>这是 Hello子组件 中定义的元素: {props.name}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<!-- 注意:这里的 ...Obj 语法,是 ES6中的属性扩散,表示:把这个对象上的所有属性,展开了,放到这个位置 -->
|
||||
<div>
|
||||
<Hello {...person}> </Hello>
|
||||
</div>,
|
||||
document.getElementById("app")
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
上方代码中,我们是想把整个person对象传递给子组件,所以采用了`...Obj 语法`语法。传递给子组件后,子组件获取的数据仅仅只是可读的。
|
||||
|
||||
## class 关键字的介绍
|
||||
|
||||
面向对象语言的三个特性:封装、继承、多态。多态 和 接口、虚拟方法有关。
|
||||
|
||||
### class的基本用法:使用class创建对象
|
||||
|
||||
myclass.js:
|
||||
|
||||
```javascript
|
||||
// 以前学习的:使用构造函数创建对象
|
||||
function Person(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
Person.prototype.say = function() {
|
||||
console.log("呵呵哒");
|
||||
};
|
||||
Person.info = 123;
|
||||
|
||||
var p1 = new Person("zs", 20);
|
||||
|
||||
|
||||
// 本次需要学习的:class 后面跟上类名,类名后面,不需要加 () ,直接上 {}
|
||||
class Per {
|
||||
// 在每个class类内部,都有一个 constructor 构造器, 如果没有显示定义 构造器,那么类内部默认都有个看不见的 constructor
|
||||
// constructor 的作用,就好比 咱们之前的 function Person(){ }
|
||||
// 每当,使用 new 关键字创建 class 类实例的时候,必然会优先调用 constructor 构造器
|
||||
// constructor(){}
|
||||
constructor(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
// 这是实例方法,必须通过 new 出来的对象调用
|
||||
say() {
|
||||
console.log("ok a ");
|
||||
}
|
||||
|
||||
static info = 123;
|
||||
|
||||
static sayHello() {
|
||||
console.log("这是静态方法");
|
||||
}
|
||||
}
|
||||
|
||||
var p2 = new Per("壹号", 26);
|
||||
console.log(p2);
|
||||
console.log(Per.info);
|
||||
console.log(Per.sayHello());
|
||||
|
||||
```
|
||||
|
||||
|
||||
### 使用 class 实现 JS 中的继承
|
||||
|
||||
myclass2.js:
|
||||
|
||||
```javascript
|
||||
class Person {
|
||||
constructor(name, age) {
|
||||
console.log(3);
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
say() {
|
||||
console.log("这是 Person中的 say 方法");
|
||||
}
|
||||
static info = 123;
|
||||
}
|
||||
|
||||
// 使用 extends 实现继承,extends的前面的是子类,后面的是父类
|
||||
class Chinese extends Person {
|
||||
constructor(name, age, color, language) {
|
||||
console.log(1);
|
||||
// 注意: 当使用 extends 关键字实现了继承, 子类的 constructor 构造函数中,必须显示调用 super() 方法,这个 super 表示父类中 constructor 的引用
|
||||
super(name, age);
|
||||
this.color = color;
|
||||
this.language = language;
|
||||
console.log(2);
|
||||
}
|
||||
}
|
||||
|
||||
var c1 = new Chinese("张三", 22, "yellow", "汉语");
|
||||
console.log(c1);
|
||||
// 父类中任何东西,子类都能继承到
|
||||
c1.say();
|
||||
|
||||
```
|
||||
|
||||
注意上方 `constructor`处的注释:当使用 extends 关键字实现了继承, 子类的 constructor 构造函数中,必须显示调用 super() 方法,这个 super 表示父类中 constructor 的引用。也就是说,在子类当中,要么不写 constructor,如果写了 constructor,就一定要把 `super()`也加上。
|
||||
|
||||
为啥我们要引入 `class`这个功能?就是因为, `class`里,永远都存在着一个 constructor。我们可以利用 `constructor`做很多事情。
|
||||
|
||||
## 创建组件的第二种方式:使用 class 关键字
|
||||
|
||||
|
||||
使用 class 创建的类,通过 extends 关键字,继承 `React.Component` 之后,这个类,就是一个组件的模板了。如果想要引用这个组件,可以把类的名称以**标签的形式**,导入到 JSX 中使用。
|
||||
|
||||
在 class 实现的组件内部,必须定义一个 render 函数。在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null。
|
||||
|
||||
|
||||
**代码举例**:
|
||||
|
||||
index.html:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta />
|
||||
<meta />
|
||||
<meta />
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 引入React相关的js库 -->
|
||||
<script type="text/javascript" src="./libs/react.js"></script>
|
||||
<script type="text/javascript" src="./libs/react-dom.js"></script>
|
||||
<script type="text/javascript" src="./libs/babel.min.js"></script>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
|
||||
<script type="text/babel">
|
||||
|
||||
// 使用 class 创建的类,通过 extends 关键字,继承 `React.Component` 之后,这个类,就是一个组件的模板了。
|
||||
// 如果想要引用这个组件,可以把类的名称以**标签的形式**,导入到 JSX 中使用。
|
||||
class Hello2 extends React.Component {
|
||||
// 在 class 实现的组件内部,必须定义一个 render 函数
|
||||
render() {
|
||||
// 在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null
|
||||
return (
|
||||
<div>
|
||||
<h3>这是使用 class 类创建的组件 </h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<Hello2> </Hello2>
|
||||
</div>,
|
||||
document.getElementById("app")
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
### 父组件传值给子组件
|
||||
|
||||
代码举例:
|
||||
|
||||
index.html:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta />
|
||||
<meta />
|
||||
<meta />
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 引入React相关的js库 -->
|
||||
<script type="text/javascript" src="./libs/react.js"></script>
|
||||
<script type="text/javascript" src="./libs/react-dom.js"></script>
|
||||
<script type="text/javascript" src="./libs/babel.min.js"></script>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
|
||||
<script type="text/babel">
|
||||
// 使用 class 创建的类,通过 extends 关键字,继承 `React.Component` 之后,这个类,就是一个组件的模板了。
|
||||
// 如果想要引用这个组件,可以把类的名称以**标签的形式**,导入到 JSX 中使用。
|
||||
class Hello2 extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
console.log(props.name);
|
||||
|
||||
// 注意:`this.state` 是固定写法,表示当前组件实例的私有数据对象,就好比 vue 中,组件实例身上的 data(){ return {} } 函数
|
||||
// 如果想要使用 组件中 state 上的数据,直接通过 this.state.*** 来访问即可
|
||||
this.state = {
|
||||
msg: "这是 Hello2 组件的私有msg数据",
|
||||
info: "永不止步"
|
||||
};
|
||||
}
|
||||
// 在 class 实现的组件内部,必须定义一个 render 函数
|
||||
render() {
|
||||
// 在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null
|
||||
return (
|
||||
<div>
|
||||
<h3>这是使用 class 类创建的组件 </h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<Hello2 name="qianguyihao"> </Hello2>
|
||||
</div>,
|
||||
document.getElementById("app")
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
## 方式一和方式二的对比
|
||||
|
||||
上面的内容里,我们使用了两种方式创建组件。这两种方式,有着本质的区别,我们来对比一下。
|
||||
|
||||
**对比**:
|
||||
|
||||
- **方式一**:通过 function构造函数 创建组件。内部没有 state 私有数据,只有 一个 props 来接收外界传递过来的数据。
|
||||
|
||||
- **方式二**:通过 class 创建子组件。内部除了有 this.props 这个只读属性之外,还有一个专门用于 存放自己私有数据的 this.state 属性,这个 state 是可读可写的。
|
||||
|
||||
基于上面的区别,我们可以为这两种创建组件的方式下定义: 使用 function 创建的组件,叫做【无状态组件】;使用 class 创建的组件,叫做【有状态组件】。
|
||||
|
||||
**本质区别**:
|
||||
|
||||
有状态组件和无状态组件,最本质的区别,就是有无 state 属性。同时, class 创建的组件,有自己的生命周期函数,但是,function 创建的 组件,没有自己的生命周期函数。
|
||||
|
||||
**什么时候使用 有状态组件,什么时候使用无状态组件**:
|
||||
|
||||
- (1)如果一个组件需要存放自己的私有数据,或者需要在组件的不同阶段执行不同的业务逻辑,此时,非常适合用 class 创建出来的有状态组件。
|
||||
|
||||
- (2)如果一个组件,只需要根据外界传递过来的 props,渲染固定的页面结构即可的话,此时,非常适合使用 function 创建出来的无状态组件。(使用无状态组件的小小好处: 由于剔除了组件的生命周期,所以,运行速度会相对快一点点)。
|
||||
|
||||
|
||||
|
||||
117
12-React基础/03-React组件(一):生命周期.md
Normal file
117
12-React基础/03-React组件(一):生命周期.md
Normal file
@@ -0,0 +1,117 @@
|
||||
|
||||
|
||||
|
||||
## 组件的生命周期
|
||||
|
||||
在组件创建、到加载到页面上运行、以及组件被销毁的过程中,总是伴随着各种各样的事件,这些在组件特定时期,触发的事件统称为组件的生命周期。
|
||||
|
||||
## 生命周期的阶段
|
||||
|
||||
组件生命周期分为三个阶段,下面分别来讲解。
|
||||
|
||||
### 1、组件创建阶段
|
||||
|
||||
> 组件创建阶段的生命周期函数,有一个显著的特点:创建阶段的生命周期函数,在组件的一辈子中,只执行一次。
|
||||
|
||||
|
||||
- getDefaultProps
|
||||
|
||||
初始化 props 属性默认值。
|
||||
|
||||
- getInitialState
|
||||
|
||||
初始化组件的私有数据。因为 state 是定义在组件的 constructor 构造器当中的,只要new 了 class类,必然会调用 constructor构造器。
|
||||
|
||||
- componentWillMount()
|
||||
|
||||
组件将要被挂载。此时还没有开始渲染虚拟DOM。
|
||||
|
||||
在这个阶段,不能去操作DOM元素,但可以操作属性、状态、function。相当于 Vue 中的Create()函数。
|
||||
|
||||
- render()
|
||||
|
||||
第一次开始渲染真正的虚拟DOM。当render执行完,内存中就有了完整的虚拟DOM了。
|
||||
|
||||
意思是,此时,虚拟DOM在内存中创建好了,但是还没有挂在到页面上。
|
||||
|
||||
在这个函数内部,不能去操作DOM元素,**因为还没return之前,虚拟DOM还没有创建**;当return执行完毕后,虚拟DOM就创建好了,但是还没有挂在到页面上。
|
||||
|
||||
- **componentDidMount()**
|
||||
|
||||
**当组件(虚拟DOM)挂载到页面之后,会进入这个生命周期函数**。
|
||||
|
||||
只要进入到这个生命周期函数,则必然说明,页面上已经有可见的DOM元素了。此时,组件已经显示到了页面上,state上的数据、内存中的虚拟DOM、以及浏览器中的页面,已经完全保持一致了。
|
||||
|
||||
当这个方法执行完,组件就进入都了 运行中 的状态。所以说,componentDidMount 是创建阶段的最后一个函数。
|
||||
|
||||
在这个函数中,我们可以放心的去 操作 页面上你需要使用的 DOM 元素了。如果我们想操作DOM元素,最早只能在 componentDidMount 中进行。相当于 Vue 中的 mounted() 函数
|
||||
|
||||
### 2、组件运行阶段
|
||||
|
||||
>有一个显著的特点,根据组件的state和props的改变,有选择性的触发0次或多次。
|
||||
|
||||
- componentWillReceiveProps()
|
||||
|
||||
组件将要接收新属性。只有当父组件中,通过某些事件,重新修改了 传递给 子组件的 props 数据之后,才会触发这个钩子函数。
|
||||
|
||||
- shouldComponentUpdate()
|
||||
|
||||
判断组件是否需要被更新。此时,组件尚未被更新,但是,state 和 props 肯定是最新的。
|
||||
|
||||
- componentWillUpdate()
|
||||
|
||||
组件将要被更新。此时,组件还没有被更新,在进入到这个生命周期函数的时候,内存中的虚拟DOM还是旧的,页面上的 DOM 元素也是旧的。(也就是说,此时操作的是旧的 DOM元素)
|
||||
|
||||
- render
|
||||
|
||||
此时,又要根据最新的 state 和 props,重新渲染一棵内存中的 虚拟DOM树。当 render 调用完毕,内存中的旧DOM树,已经被新DOM树替换了!此时,虚拟DOM树已经和组件的 state 保持一致了,都是最新的;但是页面还是旧的。
|
||||
|
||||
- componentDidUpdate
|
||||
|
||||
此时,组件完成更新,页面被重新渲染。此时,state、虚拟DOM 和 页面已经完全保持同步。
|
||||
|
||||
### 3、组件销毁阶段
|
||||
|
||||
一辈子只执行一次。
|
||||
|
||||
- componentWillUnmount: 组件将要被卸载。此时组件还可以正常使用。
|
||||
|
||||
React 生命周期的截图如下:
|
||||
|
||||
20190212_1745.jpg
|
||||
|
||||
生命周期对比:
|
||||
|
||||
- [vue中的生命周期图](https://cn.vuejs.org/v2/guide/instance.html#生命周期图示)
|
||||
|
||||
- [React Native 中组件的生命周期](http://www.race604.com/react-native-component-lifecycle/)
|
||||
|
||||
## 组件生命周期的执行顺序
|
||||
|
||||
**1、Mounting**:
|
||||
|
||||
- constructor()
|
||||
|
||||
- componentWillMount()
|
||||
|
||||
- render()
|
||||
|
||||
- componentDidMount()
|
||||
|
||||
**2、Updating**:
|
||||
|
||||
- componentWillReceiveProps(nextProps):接收父组件传递过来的属性
|
||||
|
||||
- shouldComponentUpdate(nextProps, nextState):一旦调用 setState,就会触发这个方法。方法默认 return true;如果 return false,后续的方法就不会走了。
|
||||
|
||||
- componentWillUpdate(nextProps, nextState)
|
||||
|
||||
- render()
|
||||
|
||||
- componentDidUpdate(prevProps, prevState)
|
||||
|
||||
**3、Unmounting**:
|
||||
|
||||
- componentWillUnmount()
|
||||
|
||||
|
||||
580
12-React基础/04-React组件(二):常见属性和函数.md
Normal file
580
12-React基础/04-React组件(二):常见属性和函数.md
Normal file
@@ -0,0 +1,580 @@
|
||||
|
||||
## defaultProps 和 prop-types
|
||||
|
||||
### 使用 defaultProps 设置组件的默认值
|
||||
|
||||
React 中,使用静态的 `defaultProps` 属性,来设置组件的默认属性值。
|
||||
|
||||
格式举例:
|
||||
|
||||
```javascript
|
||||
// 在 React 中,使用静态的 defaultProps 属性,来设置组件的默认属性值
|
||||
static defaultProps = {
|
||||
initcount: 0 // 如果外界没有传递 initcount,那么,自己初始化一个数值(比如0)
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
### 使用prop-types进行props数据类型的校验
|
||||
|
||||
在组件中,可以通过 `prop-types` 把外界传递过来的属性,做类型校验。如果类型不匹配,控制台会弹出告警。
|
||||
|
||||
注意:如果要为 传递过来的属性做类型校验,必须安装 React 提供的 第三方包,叫做 `prop-types`。
|
||||
|
||||
格式举例:
|
||||
|
||||
```javascript
|
||||
static propTypes = {
|
||||
initcount: ReactTypes.number // 使用 prop-types 包,来定义 initcount 为 number 类型
|
||||
};
|
||||
```
|
||||
|
||||
下方代码中,在引用组件的时候,如果类型不匹配:
|
||||
|
||||
```javascript
|
||||
// 使用 render 函数渲染 虚拟DOM
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
{/* 规定,每个用户在使用 组件的时候,必须传递一个 默认的 数值,作为 组件初始化的 数据 */}
|
||||
<Counter initcount="我是string类型"></Counter>
|
||||
</div>,
|
||||
document.getElementById("app")
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
控制台告警如下:
|
||||
|
||||
20190212_2130.png
|
||||
|
||||
|
||||
### 代码举例
|
||||
|
||||
我们把 `defaultProps` 和 `prop-types` 来举个例子。
|
||||
|
||||
(1)index.html:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 容器,通过 React 渲染得到的 虚拟DOM,会呈现到这个位置 -->
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
(2)main.js:
|
||||
|
||||
```js
|
||||
// JS打包入口文件
|
||||
// 1. 导入包
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
// 导入计数器组件
|
||||
import Counter from "./components/Counter.jsx";
|
||||
|
||||
// 使用 render 函数渲染 虚拟DOM
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
{/* 规定,每个用户在使用 组件的时候,必须传递一个 默认的 数值,作为 组件初始化的 数据 */}
|
||||
<Counter initcount={0}></Counter>
|
||||
</div>,
|
||||
document.getElementById("app")
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
|
||||
(3)/components/Counter.jsx:
|
||||
|
||||
|
||||
```javascript
|
||||
import React from "react";
|
||||
// 注意: prop-types 包中职能跟单一,只提供了 一些常见的 数据类型,用于做类型校验
|
||||
import ReactTypes from "prop-types";
|
||||
|
||||
export default class Counter extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// 初始化组件,保存的是组件的私有数据
|
||||
this.state = {
|
||||
msg: "ok",
|
||||
count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话,就把 count 值改成了可读可写的 state 属性。因此,以后就能实现“点击 按钮 ,count 值 + 1”的需求了
|
||||
};
|
||||
}
|
||||
|
||||
// 在 React 中,使用静态的 defaultProps 属性,来设置组件的默认属性值
|
||||
static defaultProps = {
|
||||
initcount: 0 // 如果外界没有传递 initcount,那么,自己初始化一个 数值,为0
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h3>这是 Counter 计数器组件 </h3>
|
||||
<p>当前的计数是:{this.state.count}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// 当 return 执行完毕后, 虚拟DOM创建好了,但是,还没有挂载到真正的页面中
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
运行效果:
|
||||
|
||||
20190212_2100.png
|
||||
|
||||
|
||||
|
||||
## 事件绑定
|
||||
|
||||
案例:点击按钮后,计数器 +1。
|
||||
|
||||
### 原生js做事件绑定
|
||||
|
||||
代码举例:
|
||||
|
||||
(1)index.html:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 容器,通过 React 渲染得到的 虚拟DOM,会呈现到这个位置 -->
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
(2)main.js:
|
||||
|
||||
```js
|
||||
// JS打包入口文件
|
||||
// 1. 导入包
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
// 导入计数器组件
|
||||
import Counter from "./components/Counter.jsx";
|
||||
|
||||
// 使用 render 函数渲染 虚拟DOM
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
{/* 规定,每个用户在使用 组件的时候,必须传递一个 默认的 数值,作为 组件初始化的 数据 */}
|
||||
<Counter initcount={0}></Counter>
|
||||
</div>,
|
||||
document.getElementById("app")
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
|
||||
(3)/components/Counter.jsx:
|
||||
|
||||
|
||||
```java
|
||||
import React from "react";
|
||||
// 注意: prop-types 包的职能跟单一,只提供了 一些常见的 数据类型,用于做类型校验
|
||||
import ReactTypes from "prop-types";
|
||||
|
||||
export default class Counter extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// 初始化组件,保存的是组件的私有数据
|
||||
this.state = {
|
||||
msg: "ok",
|
||||
count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话,就把 count 值改成了可读可写的 state 属性。因此,以后就能实现“点击 按钮 ,count 值 + 1”的需求了
|
||||
};
|
||||
}
|
||||
|
||||
// 在 React 中,使用静态的 defaultProps 属性,来设置组件的默认属性值
|
||||
static defaultProps = {
|
||||
initcount: 0 // 如果外界没有传递 initcount,那么,自己初始化一个数值(比如0)
|
||||
};
|
||||
|
||||
// 这是创建一个 静态的 propTypes 对象,在这个对象中,可以把 外界传递过来的属性,做类型校验
|
||||
static propTypes = {
|
||||
initcount: ReactTypes.number // 使用 prop-types 包,来定义 initcount 为 number 类型
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h3>这是 Counter 计数器组件 </h3>
|
||||
<input type="button" value="+1" id="btn" />
|
||||
<p>当前的计数是:{this.state.count}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// 当 return 执行完毕后, 虚拟DOM创建好了,但是,还没有挂载到真正的页面中
|
||||
}
|
||||
|
||||
// 当组件挂载到页面上之后,会进入这个生命周期函数,只要进入这个生命周期函数了,必然说明,页面上,已经有可见的DOM元素了
|
||||
componentDidMount() {
|
||||
// 在这个函数中,我们可以放心的去 操作 页面上你需要使用的 DOM 元素了。
|
||||
// 也就是说,如果我们想操作DOM元素,最早,只能在 componentDidMount 中进行。
|
||||
document.getElementById("btn").onclick = () => {
|
||||
this.setState({
|
||||
count: this.state.count + 1
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 使用 React 提供的方法,做事件绑定
|
||||
|
||||
代码举例:
|
||||
|
||||
(1)index.html和 (2)main.js 的代码不变,和上一小段中的代码一致。
|
||||
|
||||
(3)/components/Counter.jsx:
|
||||
|
||||
```java
|
||||
import React from "react";
|
||||
// 注意: prop-types 包的职能跟单一,只提供了 一些常见的 数据类型,用于做类型校验
|
||||
import ReactTypes from "prop-types";
|
||||
|
||||
export default class Counter extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// 初始化组件,保存的是组件的私有数据
|
||||
this.state = {
|
||||
msg: "ok",
|
||||
count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话,就把 count 值改成了可读可写的 state 属性。因此,以后就能实现“点击 按钮 ,count 值 + 1”的需求了
|
||||
};
|
||||
}
|
||||
|
||||
// 在 React 中,使用静态的 defaultProps 属性,来设置组件的默认属性值
|
||||
static defaultProps = {
|
||||
initcount: 0 // 如果外界没有传递 initcount,那么,自己初始化一个数值(比如0)
|
||||
};
|
||||
|
||||
// 这是创建一个 静态的 propTypes 对象,在这个对象中,可以把 外界传递过来的属性,做类型校验
|
||||
static propTypes = {
|
||||
initcount: ReactTypes.number // 使用 prop-types 包,来定义 initcount 为 number 类型
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h3>这是 Counter 计数器组件 </h3>
|
||||
{/* 这里的 this 指向的是 Counter 组件的实例 */}
|
||||
<input type="button" value="+1" id="btn" onClick={this.myMethod} />
|
||||
<p>当前的计数是:{this.state.count}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// 当 return 执行完毕后, 虚拟DOM创建好了,但是,还没有挂载到真正的页面中
|
||||
}
|
||||
|
||||
// 点击事件的方法定义
|
||||
myMethod = () => {
|
||||
// 修改组件的state里面的值
|
||||
this.setState({
|
||||
count: this.state.count + 1
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## 生命周期函数:shouldComponentUpdate()
|
||||
|
||||
在 shouldComponentUpdate() 函数中,必须要求返回一个**布尔值**。
|
||||
|
||||
**需要注意的是**:如果返回的值是 false,则不会继续执行后续的生命周期函数,而是直接退回到了 运行中 的状态。因为此时,**后续的 render 函数并没有被调用**,因此页面不会被更新,但是组件的 state 状态,却被修改了。这种情况,我们也可以这样理解:如果返回值为 false,此时只是更新了 state 里面的数值,但是并没有渲染到 DOM节点上。
|
||||
|
||||
利用上面这个特性,我们可以来举个例子。
|
||||
|
||||
**举例**:实现 Counter 计数器只在偶数情况下更新。
|
||||
|
||||
实现思路:在 shouldComponentUpdate() 函数中,如果 state 中 的count 的值为奇数,就 return false;否则就 return true。
|
||||
|
||||
代码实现:(我们在上面的`Counter.jsx`代码基础之上,做添加)
|
||||
|
||||
|
||||
```javascript
|
||||
// 判断组件是否需要更新
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
|
||||
// 经过打印测试发现:在 shouldComponentUpdate 中,通过 this.state.count 拿到的值,是上一次的旧数据,并不是当前最新的;
|
||||
// 解决办法:通过 shouldComponentUpdate 函数的第二个参数 nextState,可以拿到 最新的 state 数据。
|
||||
|
||||
console.log(this.state.count + " ---- " + nextState.count);
|
||||
|
||||
// 需求: 如果 state 中的 count 值是偶数,则 更新页面;如果 count 值 是奇数,则不更新页面。最终实现的的页面效果:2,4,6,8,10,12....
|
||||
// return this.state.count % 2 === 0 ? true : false
|
||||
return nextState.count % 2 === 0 ? true : false;
|
||||
}
|
||||
```
|
||||
|
||||
上面这部分的代码,和 render() 方法是并列的。我们需要注意里面的注释,关注 nextState 参数的用法。
|
||||
|
||||
|
||||
## 在js代码中获取html标签的属性
|
||||
|
||||
比如说,如果想获取 html标签的 innerHTML 属性,做法如下:
|
||||
|
||||
通过原生 js 获取:
|
||||
|
||||
```javascript
|
||||
document.getElementById('myh3').innerHTML
|
||||
```
|
||||
|
||||
也可以通过 React 提供的 `refs` 获取:
|
||||
|
||||
|
||||
```javascript
|
||||
this.refs.h3.innerHTML
|
||||
```
|
||||
|
||||
代码举例:
|
||||
|
||||
(3)/components/Counter.jsx:
|
||||
|
||||
|
||||
```java
|
||||
import React from "react";
|
||||
// 注意: prop-types 包的职能跟单一,只提供了 一些常见的 数据类型,用于做类型校验
|
||||
import ReactTypes from "prop-types";
|
||||
|
||||
export default class Counter extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// 初始化组件,保存的是组件的私有数据
|
||||
this.state = {
|
||||
msg: "ok",
|
||||
count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话,就把 count 值改成了可读可写的 state 属性。因此,以后就能实现“点击 按钮 ,count 值 + 1”的需求了
|
||||
};
|
||||
}
|
||||
|
||||
// 在 React 中,使用静态的 defaultProps 属性,来设置组件的默认属性值
|
||||
static defaultProps = {
|
||||
initcount: 0 // 如果外界没有传递 initcount,那么,自己初始化一个数值(比如0)
|
||||
};
|
||||
|
||||
// 这是创建一个 静态的 propTypes 对象,在这个对象中,可以把 外界传递过来的属性,做类型校验
|
||||
static propTypes = {
|
||||
initcount: ReactTypes.number // 使用 prop-types 包,来定义 initcount 为 number 类型
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h3>这是 Counter 计数器组件 </h3>
|
||||
{/* 这里的 this 指向的是 Counter 组件的实例 */}
|
||||
<input type="button" value="+1" id="btn" onClick={this.myMethod} />
|
||||
<h3 id="myh3" ref="mymyh3">
|
||||
当前的计数是:{this.state.count}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// 当 return 执行完毕后, 虚拟DOM创建好了,但是,还没有挂载到真正的页面中
|
||||
}
|
||||
|
||||
// 点击事件的方法定义
|
||||
myMethod = () => {
|
||||
// 修改组件的state里面的值
|
||||
this.setState({
|
||||
count: this.state.count + 1
|
||||
});
|
||||
};
|
||||
|
||||
// 判断组件是否需要更新
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
// 需求: 如果 state 中的 count 值是偶数,则 更新页面;如果 count 值 是奇数,则不更新页面。最终实现的的页面效果:2,4,6,8,10,12....
|
||||
|
||||
// 经过打印测试发现:在 shouldComponentUpdate 中,通过 this.state.count 拿到的值,是上一次的旧数据,并不是当前最新的;
|
||||
// 解决办法:通过 shouldComponentUpdate 函数的第二个参数 nextState,可以拿到 最新的 state 数据。
|
||||
|
||||
console.log(this.state.count + " ---- " + nextState.count);
|
||||
// return this.state.count % 2 === 0 ? true : false
|
||||
// return nextState.count % 2 === 0 ? true : false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 组件将要更新。此时尚未更新,在进入这个 生命周期函数的时候,内存中的虚拟DOM是旧的,页面上的 DOM 元素 也是旧的
|
||||
componentWillUpdate() {
|
||||
// 经过打印分析发现:此时页面上的 DOM 节点,都是旧的,应该慎重操作,因为你可能操作的是旧DOM
|
||||
// console.log(document.getElementById('myh3').innerHTML)
|
||||
console.log(this.refs.mymyh3.innerHTML);
|
||||
}
|
||||
|
||||
// 组件完成了更新。此时,state 中的数据、虚拟DOM、页面上的DOM,都是最新的,此时,你可以放心大胆的去操作页面了
|
||||
componentDidUpdate() {
|
||||
console.log(this.refs.mymyh3.innerHTML);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
上方代码中,componentWillUpdate() 和 componentDidUpdate() 方法里的代码,就是我们这一段要举的例子。
|
||||
|
||||
需要注意的是,`<h3 id="myh3" ref="mymyh3">`这部分代码中,属性名只能小写,不能大写。
|
||||
|
||||
工程文件:
|
||||
|
||||
- [2019-02-12-ReactDemo.zip](https://pan.baidu.com/s/1STNpgtmO23hE_cHIjNkBMA)
|
||||
|
||||
|
||||
## 生命周期函数:componentWillReceiveProps()
|
||||
|
||||
|
||||
当子组件第一次被渲染到页面上的时候,不会触发这个 函数。
|
||||
|
||||
只有当父组件中,通过 某些 事件,重新修改了 传递给 子组件的 props 数据之后,才会触发 componentWillReceiveProps。
|
||||
|
||||
代码举例:
|
||||
|
||||
(1)index.html:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 容器,通过 React 渲染得到的 虚拟DOM,会呈现到这个位置 -->
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
(2)main.js:(引入组件)
|
||||
|
||||
|
||||
```javascript
|
||||
// JS打包入口文件
|
||||
// 1. 导入包
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import MyParent from "./components/TestReceiveProps.jsx";
|
||||
|
||||
// 使用 render 函数渲染 虚拟DOM
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<MyParent></MyParent>
|
||||
</div>,
|
||||
document.getElementById("app")
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
|
||||
(3)TestReceiveProps.jsx:(组件的定义)
|
||||
|
||||
```javascript
|
||||
import React from "react";
|
||||
|
||||
// 父组件
|
||||
export default class Parent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
msg: "这是父组件中的 msg 消息"
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>这是父组件</h1>
|
||||
<input
|
||||
type="button"
|
||||
value="点击修改父组件的 MSG"
|
||||
onClick={this.changeMsg}
|
||||
/>
|
||||
<hr />
|
||||
{/* 在父组件 Parent 中引用子组件 Son */}
|
||||
<Son pmsg={this.state.msg} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
changeMsg = () => {
|
||||
this.setState({
|
||||
msg: "修改组件的msg为新的值"
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 子组件
|
||||
class Son extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>这是子组件 --- {this.props.pmsg}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 组件将要接收外界传递过来的新的 props 属性值
|
||||
// 当子组件第一次被渲染到页面上的时候,不会触发这个 函数;
|
||||
// 只有当 父组件中,通过 某些 事件,重新修改了 传递给 子组件的 props 数据之后,才会触发 componentWillReceiveProps
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// console.log('被触发了!');
|
||||
// 注意: 在 componentWillReceiveProps 被触发的时候,如果我们使用 this.props 来获取属性值,这个属性值,不是最新的,是上一次的旧属性值
|
||||
// 如果想要获取最新的属性值,需要通过 componentWillReceiveProps 的参数列表来获取
|
||||
console.log(this.props.pmsg + " ---- " + nextProps.pmsg);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
上方代码中,我们在组件 Parent 中引入了子组件 Son。重点注意 componentWillReceiveProps()函数 的注释部分。
|
||||
|
||||
|
||||
|
||||
244
12-React基础/05-React中绑定this并给函数传参的几种方式.md
Normal file
244
12-React基础/05-React中绑定this并给函数传参的几种方式.md
Normal file
@@ -0,0 +1,244 @@
|
||||
|
||||
|
||||
## 前言
|
||||
|
||||
我们先来看下面这段代码:
|
||||
|
||||
components/MyComponent.jsx
|
||||
|
||||
```javascript
|
||||
import React from "react";
|
||||
|
||||
export default class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
msg: "这是 MyComponent 组件 默认的msg"
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>绑定This并传参</h1>
|
||||
<input type="button" value="绑定this并传参" onClick={this.changeMsg} />
|
||||
<h3>{this.state.msg}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
changeMsg() {
|
||||
// 注意:这里的changeMsg()只是一个普通方法。因此,在触发的时候,这里的 this 是 undefined
|
||||
console.log(this); // 打印结果:undefined
|
||||
this.setState({
|
||||
msg: "设置 msg 为新的值"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
上面的代码中,点击按钮,执行 changeMsg() 方法,尝试修改 this.state.msg 的值。但是,这个方法执行的时候,是会报错的:
|
||||
|
||||
```
|
||||
Uncaught TypeError: Cannot read property 'setState' of null
|
||||
```
|
||||
|
||||
而且,打印this的结果也是 undefined。这是为啥呢?因为这里的 this 并不是指向 MyComponent 组件本身。
|
||||
|
||||
那如何让 changeMsg() 方法里面的 this,指向MyComponent 组件呢?办法总是有的,比如说,将changeMsg() 修改为箭头函数:
|
||||
|
||||
```javascript
|
||||
changeMsg = () => {
|
||||
console.log(this); // 打印结果:MyComponent 组件
|
||||
this.setState({
|
||||
msg: "设置 msg 为新的值"
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
那么,除了箭头函数可以 绑定 this,还有没有其他的方式呢?我们接下来讲一讲。
|
||||
|
||||
|
||||
## 绑定 this 的方式一:bind()
|
||||
|
||||
代码举例:
|
||||
|
||||
```javascript
|
||||
import React from "react";
|
||||
|
||||
export default class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
msg: "这是 MyComponent 组件 默认的msg"
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>绑定This并传参</h1>
|
||||
{/* bind 的作用:为前面的函数,修改函数内部的 this 指向。让 函数内部的this,指向 bind 参数列表中的 第一个参数 */}
|
||||
<input
|
||||
type="button"
|
||||
value="绑定this并传参"
|
||||
onClick={this.changeMsg1.bind(this)}
|
||||
/>
|
||||
<h3>{this.state.msg}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
changeMsg1() {
|
||||
this.setState({
|
||||
msg: "设置 msg 为新的值"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
上方代码中,我们为什么用 bind(),而不是用 call/apply 呢?因为 bind() 并不会立即调用,正是我们需要的。
|
||||
|
||||
**注意**:bind 中的第一个参数,是用来修改 this 指向的。第一个参数**后面的所有参数**,都将作为函数的参数传递进去。
|
||||
|
||||
我们来看看通过 bind() 是怎么传参的。
|
||||
|
||||
**通过 bind() 绑定this,并给函数传参**:
|
||||
|
||||
|
||||
```javascript
|
||||
import React from "react";
|
||||
|
||||
export default class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
msg: "这是 MyComponent 组件 默认的msg"
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>绑定This并传参</h1>
|
||||
{/* bind 的作用:为前面的函数,修改函数内部的 this 指向。让 函数内部的this,指向 bind 参数列表中的 第一个参数 */}
|
||||
<input type="button" value="绑定this并传参" onClick={this.changeMsg1.bind(this, "千古啊", "壹号啊")} />
|
||||
<h3>{this.state.msg}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
changeMsg1(arg1, arg2) {
|
||||
this.setState({
|
||||
msg: "设置 msg 为新的值" + arg1 + arg2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## 绑定 this 并给函数传参 的方式二:构造函数里设置 bind()
|
||||
|
||||
我们知道,构造函数中的 this 本身就是指向组件的实例的,所以,我们可以在这里做一些事情。
|
||||
|
||||
代码举例:
|
||||
|
||||
```javascript
|
||||
import React from "react";
|
||||
|
||||
export default class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
msg: "这是 MyComponent 组件 默认的msg"
|
||||
};
|
||||
|
||||
// 绑定 this 并给函数传参的方式2: 在构造函数中绑定并传参
|
||||
// 注意:当一个函数调用 bind 改变了this指向后,bind 函数调用的结果,有一个【返回值】,这个值,就是被改变this指向后的函数的引用。
|
||||
// 也就是说: bind 不会修改 原函数的 this 指向,而是改变了 “函数拷贝”的this指向。
|
||||
this.changeMsg2 = this.changeMsg2.bind(this, "千古恩", "壹号恩");
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>绑定This并传参</h1>
|
||||
<input type="button" value="绑定this并传参" onClick={this.changeMsg2} />
|
||||
<h3>{this.state.msg}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
changeMsg2(arg1, arg2) {
|
||||
this.setState({
|
||||
msg: "设置 msg 为新的值" + arg1 + arg2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
上方代码中,需要注意的是:当一个函数调用 bind 改变了this指向后,bind 函数调用的结果,有一个【返回值】,这个值,就是被改变this指向后的函数的引用。也就是说: bind 不会修改 原函数的 this 指向,而是改变了 “函数拷贝”的this指向。
|
||||
|
||||
|
||||
## 绑定 this 并给函数传参 的方式三:箭头函数【荐】
|
||||
|
||||
第三种方式用得最多。
|
||||
|
||||
代码举例:
|
||||
|
||||
|
||||
```javascript
|
||||
import React from "react";
|
||||
|
||||
export default class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
msg: "这是 MyComponent 组件 默认的msg"
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>绑定This并传参</h1>
|
||||
<input
|
||||
type="button"
|
||||
value="绑定this并传参"
|
||||
onClick={() => {
|
||||
this.changeMsg3("千古3", "壹号3");
|
||||
}}
|
||||
/>
|
||||
<h3>{this.state.msg}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
changeMsg3 = (arg1, arg2) => {
|
||||
// console.log(this);
|
||||
// 注意:这里的方式,是一个普通方法,因此,在触发的时候,这里的 this 是 undefined
|
||||
this.setState({
|
||||
msg: "绑定this并传参的方式3:" + arg1 + arg2
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
165
12-React基础/06-React的单向数据绑定.md
Normal file
165
12-React基础/06-React的单向数据绑定.md
Normal file
@@ -0,0 +1,165 @@
|
||||
|
||||
|
||||
## 单项数据绑定
|
||||
|
||||
|
||||
在 Vue 中,可以通过 v-model 指令来实现双向数据绑定。但是,在 React 中并没有指令的概念,而且 **React 默认不支持 双向数据绑定**。
|
||||
|
||||
React 只支持,把数据从 state 上传输到 页面,但是,无法自动实现数据从 页面 传输到 state 中 进行保存。
|
||||
|
||||
React中,只支持单项数据绑定,不支持双向数据绑定。不信的话,我们来看下面这个例子:
|
||||
|
||||
|
||||
```java
|
||||
import React from "react";
|
||||
|
||||
export default class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
msg: "这是 MyComponent 组件 默认的msg"
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>呵呵哒</h3>
|
||||
<input type="text" value={this.state.msg} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
上方代码中,我们尝试在 input文本框中读取 state.msg 的值,运行结果中,却弹出了警告:
|
||||
|
||||
20190213_2000.png
|
||||
|
||||
```
|
||||
Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.
|
||||
```
|
||||
|
||||
## 通过onChange方法,实现双向数据绑定
|
||||
|
||||
如果针对 表单元素做 value 属性绑定,那么,必须同时为 表单元素 绑定 readOnly, 或者提供 onChange 事件:
|
||||
|
||||
- 如果是绑定readOnly,表示这个元素只读,不能被修改。此时,控制台就不会弹出警告了。
|
||||
|
||||
- 如果是绑定onChange,表示这个元素的值可以被修改,但是,要自己定义修改的逻辑。
|
||||
|
||||
绑定readOnly的举例如下:(表示value中的数据是只读的)
|
||||
|
||||
```javascript
|
||||
<input type="text" value={this.state.msg} readOnly />
|
||||
```
|
||||
|
||||
**绑定 onChange 的举例如下**:(通过onChange方法,实现双向数据绑定)
|
||||
|
||||
(1)index.html:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 容器,通过 React 渲染得到的 虚拟DOM,会呈现到这个位置 -->
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
|
||||
(2)main.js:
|
||||
|
||||
```javascript
|
||||
// JS打包入口文件
|
||||
// 1. 导入包
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
// 导入组件
|
||||
import MyComponent from "./components/MyComponent.jsx";
|
||||
|
||||
// 使用 render 函数渲染 虚拟DOM
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<MyComponent></MyComponent>
|
||||
</div>,
|
||||
document.getElementById("app")
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
(3)components/MyComponent.jsx
|
||||
|
||||
```javascript
|
||||
import React from "react";
|
||||
|
||||
export default class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
msg: "这是组件 默认的msg"
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>呵呵哒</h1>
|
||||
<input
|
||||
type="text" value={this.state.msg} onChange={this.txtChanged} ref="txt" />
|
||||
<h3>{"实时显示msg中的内容:" + this.state.msg}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 为 文本框 绑定 txtChanged 事件
|
||||
txtChanged = (e) => {
|
||||
// 获取 <input> 文本框中 文本的3种方式:
|
||||
// 方式一:使用 document.getElementById
|
||||
|
||||
// 方式二:使用 ref
|
||||
// console.log(this.refs.txt.value);
|
||||
|
||||
// 方式三:使用 事件对象的 参数 e 来拿
|
||||
// 此时,e.target 就表示触发 这个事件的 事件源对象,得到的是一个原生的JS DOM 对象。在这个案例里,e.target就是指文本框
|
||||
// console.log(e.target.value);
|
||||
this.setState({
|
||||
msg: e.target.value
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
工程文件:
|
||||
|
||||
- [2019-02-13-ReactDemo.zip]()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
253
12-React基础/07-React路由的使用.md
Normal file
253
12-React基础/07-React路由的使用.md
Normal file
@@ -0,0 +1,253 @@
|
||||
|
||||
## React路由的使用
|
||||
|
||||
使用React路由之前,我们需要先安装 `react-router-dom`这个包。比如:
|
||||
|
||||
```
|
||||
yarn add react-router-dom
|
||||
```
|
||||
|
||||
代码举例:
|
||||
|
||||
(1)index.html
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 容器,通过 React 渲染得到的 虚拟DOM,会呈现到这个位置 -->
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
(2)main.js:
|
||||
|
||||
```javascript
|
||||
// JS打包入口文件
|
||||
// 1. 导入包
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import App from "./App.jsx";
|
||||
|
||||
// 使用 render 函数渲染 虚拟DOM
|
||||
ReactDOM.render(<App />, document.getElementById("app"));
|
||||
|
||||
```
|
||||
|
||||
(3)app.jsx:
|
||||
|
||||
```java
|
||||
import React from "react";
|
||||
|
||||
// 如果要使用 路由模块,第一步,运行 yarn add react-router-dom
|
||||
// 第二步,导入 路由模块
|
||||
|
||||
// HashRouter 表示一个路由的跟容器,将来,所有的路由相关的东西,都要包裹在 HashRouter 里面,而且,一个网站中,只需要使用一次 HashRouter 就好了;
|
||||
// Route 表示一个路由规则, 在 Route 上,有两个比较重要的属性, path component
|
||||
// Link 表示一个路由的链接 ,就好比 vue 中的 <router-link to=""></router-link>
|
||||
import { HashRouter, Route, Link } from "react-router-dom";
|
||||
|
||||
import Home from "./components/Home.jsx";
|
||||
import Movie from "./components/Movie.jsx";
|
||||
import About from "./components/About.jsx";
|
||||
|
||||
export default class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
// 当 使用 HashRouter 把 App 根组件的元素包裹起来之后,网站就已经启用路由了
|
||||
// 在一个 HashRouter 中,只能有唯一的一个根元素
|
||||
// 在一个网站中,只需要使用 唯一的一次 <HashRouter></HashRouter> 即可
|
||||
return (
|
||||
<HashRouter>
|
||||
<div>
|
||||
<h1>这是网站的APP根组件</h1>
|
||||
<hr />
|
||||
|
||||
<Link to="/home">首页</Link>
|
||||
<Link to="/movie">电影</Link>
|
||||
<Link to="/about">关于</Link>
|
||||
<hr />
|
||||
|
||||
{/* Route 创建的标签,就是路由规则,其中 path 表示要匹配的路由,component 表示要展示的组件 */}
|
||||
{/* 在 vue 中有个 router-view 的路由标签,专门用来放置,匹配到的路由组件的,但是,在 react-router 中,并没有类似于这样的标签,而是 ,直接把 Route 标签,当作的 坑(占位符) */}
|
||||
{/* Route 具有两种身份:1. 它是一个路由匹配规则; 2. 它是 一个占位符,表示将来匹配到的组件都放到这个位置 */}
|
||||
<Route path="/home" component={Home} />
|
||||
<hr />
|
||||
<Route path="/movie" component={Movie} />
|
||||
<hr />
|
||||
<Route path="/about" component={About} />
|
||||
</div>
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
(4)ReactDemo/src/components/Home.jsx
|
||||
|
||||
```java
|
||||
import React from "react";
|
||||
|
||||
export default class Home extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>Home组件</div>;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
(5)ReactDemo/src/components/Movie.jsx
|
||||
|
||||
```java
|
||||
|
||||
import React from "react";
|
||||
|
||||
export default class Movie extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>Movie组件</div>;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
(6)ReactDemo/src/components/About.jsx
|
||||
|
||||
```java
|
||||
import React from "react";
|
||||
|
||||
export default class About extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>About组件</div>;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
20190214_1000.png
|
||||
|
||||
## 匹配路由参数
|
||||
|
||||
### 模糊匹配与精准匹配
|
||||
|
||||
我们在上面的代码中,进一步修改。假设 Movie 这个组件修改成这种路由匹配方式:
|
||||
|
||||
```html
|
||||
<Link to="/movie/top250">电影</Link>
|
||||
|
||||
<Route path="/movie" component={Movie} />
|
||||
|
||||
```
|
||||
|
||||
上面这种匹配方式,也是可以成功匹配到的。这是为啥呢?
|
||||
|
||||
这是因为:默认情况下,路由中的匹配规则,是**模糊匹配**的。如果 路由可以部分匹配成功,就会展示这个路由对应的组件。
|
||||
|
||||
如果想让路由规则,进行**精确匹配**,可以为Route添加 `exact` 属性。比如下面这种写法,因为是开启了精准匹配,所以是匹配不到的:(无法匹配)
|
||||
|
||||
```html
|
||||
<Link to="/movie/top250/20">电影</Link>
|
||||
|
||||
<Route path="/movie/" component={Movie} exact/>
|
||||
```
|
||||
|
||||
|
||||
另外,如果要匹配参数,可以在匹配规则中,使用 `:` 修饰符,表示这个位置匹配到的是参数。举例如下:(匹配正常)
|
||||
|
||||
```html
|
||||
<Link to="/movie/top250/20">电影</Link>
|
||||
|
||||
<Route path="/movie/:type/:id" component={Movie} exact/>
|
||||
```
|
||||
|
||||
|
||||
### 获取路由参数
|
||||
|
||||
继续修改上面的代码。如果我想在 Movie 组件中显示路由中的参数,怎么做呢?
|
||||
|
||||
我们可以通过 `props.match.params`获取路由中的参数。举例做法如下:
|
||||
|
||||
app.jsx中的匹配规则如下:
|
||||
|
||||
|
||||
```html
|
||||
<Link to="/movie/top100/5">电影</Link>
|
||||
|
||||
<Route path="/movie/:type/:id" component={Movie} exact/>
|
||||
```
|
||||
|
||||
|
||||
Moivie 组件的写法如下:
|
||||
|
||||
```java
|
||||
import React from "react";
|
||||
|
||||
export default class Movie extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
routeParams: props.match.params // 把路由中的参数保存到 state 中
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log(this);
|
||||
// 如果想要从路由规则中,提取匹配到的参数,进行使用,可以使用 this.props.match.params.*** 来访问
|
||||
return (
|
||||
<div>
|
||||
{/* Movie --- {this.props.match.params.type} --- {this.props.match.params.id} */}
|
||||
Movie --- {this.state.routeParams.type} --- {this.state.routeParams.id}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
打印结果如下:
|
||||
|
||||
20190214_1030.png
|
||||
|
||||
|
||||
工程文件:
|
||||
|
||||
2019-02-14-ReactDemo.zip
|
||||
|
||||
## 参考链接
|
||||
|
||||
|
||||
|
||||
150
12-React基础/08-Ant Design的基本使用.md
Normal file
150
12-React基础/08-Ant Design的基本使用.md
Normal file
@@ -0,0 +1,150 @@
|
||||
|
||||
## andt 的介绍
|
||||
|
||||
Ant Design 是基于 React 实现,开发和服务于企业级后台产品。
|
||||
|
||||
### 支持环境
|
||||
|
||||
- 现代浏览器和 IE9 及以上(需要 polyfills)。
|
||||
|
||||
- 支持服务端渲染。
|
||||
|
||||
- [Electron](https://electronjs.org/)
|
||||
|
||||
|
||||
Electron(原名为Atom Shell)是GitHub开发的一个开源框架。 它允许使用Node.js(作为后端)和Chromium(作为前端)完成桌面GUI应用程序的开发。
|
||||
|
||||
很多客户端软件都是基于 Electron 开发的。比如 VS Code。我们打开 VS Code 菜单栏的 “帮助 --> 切换开发人员工具”,就会看到类似于 chrome的调试工具。
|
||||
|
||||
|
||||
### 相关链接
|
||||
|
||||
- 官方文档:<https://ant.design/docs/react/introduce-cn>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## andt 的使用
|
||||
|
||||
|
||||
|
||||
### 环境安装
|
||||
|
||||
```
|
||||
npm install antd --save
|
||||
```
|
||||
|
||||
### 代码示例
|
||||
|
||||
我们需要什么组件,就导入该组件即可。
|
||||
|
||||
|
||||
(1)index.html:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 容器,通过 React 渲染得到的 虚拟DOM,会呈现到这个位置 -->
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
(2)main.js:
|
||||
|
||||
```java
|
||||
// JS打包入口文件
|
||||
// 1. 导入包
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import MyComponent from "./components/MyComponent.jsx";
|
||||
|
||||
// 使用 render 函数渲染 虚拟DOM
|
||||
ReactDOM.render(<MyComponent></MyComponent>, document.getElementById("app"));
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
(3)MyComponent.jsx:
|
||||
|
||||
```java
|
||||
import React from "react";
|
||||
|
||||
// 导入 日期选择组件
|
||||
import { DatePicker } from "antd";
|
||||
|
||||
export default class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h3>在组件中引入 andt</h3>
|
||||
|
||||
<DatePicker />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
代码运行效果:
|
||||
|
||||
20190217_1500.png
|
||||
|
||||
|
||||
## AntD组件
|
||||
|
||||
### 表格
|
||||
|
||||
|
||||
`pagination`属性可以用来分页。
|
||||
|
||||
|
||||
### loading框
|
||||
|
||||
需求:在数据显示之前,展示 loading;在数据显示之后,关闭loading。
|
||||
|
||||
|
||||
## 相关问题的链接
|
||||
|
||||
|
||||
|
||||
|
||||
### AntD pro,跳转到详情页,携带参数
|
||||
|
||||
- [ant design列表页,转跳到详情页,携带参数](https://blog.csdn.net/u011613356/article/details/81505883)
|
||||
|
||||
- [ant design pro商品页带参数转到详情页](https://blog.csdn.net/ws995339251/article/details/86771701)
|
||||
|
||||
|
||||
### AntD pro ,必填项前面,显示星号
|
||||
|
||||
- [表单必填项label上的红色*号是怎么出现的](https://github.com/ant-design/ant-design-pro/issues/2044)
|
||||
|
||||
### 其他问题
|
||||
|
||||
- 面包屑层级显示问题:<https://github.com/ant-design/ant-design-pro/issues/1584>
|
||||
|
||||
- from验证input框只能输入数字:<https://blog.csdn.net/zr15829039341/article/details/82745239>
|
||||
|
||||
|
||||
618
12-React基础/09-AntD框架的upload组件上传图片时遇到的一些坑.md
Normal file
618
12-React基础/09-AntD框架的upload组件上传图片时遇到的一些坑.md
Normal file
@@ -0,0 +1,618 @@
|
||||
|
||||
## 前言
|
||||
|
||||
本次做后台管理系统,采用的是 AntD 框架。涉及到图片的上传,用的是AntD的 [upload](https://ant.design/components/upload-cn/) 组件。
|
||||
|
||||
前端做文件上传这个功能,是很有技术难度的。既然框架给我们提供好了,那就直接用呗。结果用的时候,发现 upload 组件的很多bug。下面来列举几个。
|
||||
|
||||
备注:本文写于2019-03-02,使用的 antd 版本是 3.13.6。
|
||||
|
||||
## 使用 AntD 的 upload 组件做图片的上传
|
||||
|
||||
因为需要上传多张图片,所以采用的是照片墙的形式。上传成功后的界面如下:
|
||||
|
||||
(1)上传中:
|
||||
|
||||

|
||||
|
||||
(2)上传成功:
|
||||
|
||||

|
||||
|
||||
(3)图片预览:
|
||||
|
||||

|
||||
|
||||
按照官方提供的实例,特此整理出项目开发中的完整写法,亲测有效。代码如下:
|
||||
|
||||
```javascript
|
||||
/* eslint-disable */
|
||||
|
||||
import { Upload, Icon, Modal, Form } from 'antd';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
class PicturesWall extends PureComponent {
|
||||
state = {
|
||||
previewVisible: false,
|
||||
previewImage: '',
|
||||
imgList: [],
|
||||
};
|
||||
|
||||
|
||||
handleChange = ({ file, fileList }) => {
|
||||
console.log(JSON.stringify(file)); // file 是当前正在上传的 单个 img
|
||||
console.log(JSON.stringify(fileList)); // fileList 是已上传的全部 img 列表
|
||||
|
||||
this.setState({
|
||||
imgList: fileList,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
handleCancel = () => this.setState({ previewVisible: false });
|
||||
|
||||
handlePreview = file => {
|
||||
this.setState({
|
||||
previewImage: file.url || file.thumbUrl,
|
||||
previewVisible: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 参考链接:https://www.jianshu.com/p/f356f050b3c9
|
||||
handleBeforeUpload = file => {
|
||||
//限制图片 格式、size、分辨率
|
||||
const isJPG = file.type === 'image/jpeg';
|
||||
const isJPEG = file.type === 'image/jpeg';
|
||||
const isGIF = file.type === 'image/gif';
|
||||
const isPNG = file.type === 'image/png';
|
||||
if (!(isJPG || isJPEG || isGIF || isPNG)) {
|
||||
Modal.error({
|
||||
title: '只能上传JPG 、JPEG 、GIF、 PNG格式的图片~',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
Modal.error({
|
||||
title: '超过2M限制,不允许上传~',
|
||||
});
|
||||
return;
|
||||
}
|
||||
return (isJPG || isJPEG || isGIF || isPNG) && isLt2M && this.checkImageWH(file);
|
||||
};
|
||||
|
||||
//返回一个 promise:检测通过则返回resolve;失败则返回reject,并阻止图片上传
|
||||
checkImageWH(file) {
|
||||
let self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
let filereader = new FileReader();
|
||||
filereader.onload = e => {
|
||||
let src = e.target.result;
|
||||
const image = new Image();
|
||||
image.onload = function() {
|
||||
// 获取图片的宽高,并存放到file对象中
|
||||
console.log('file width :' + this.width);
|
||||
console.log('file height :' + this.height);
|
||||
file.width = this.width;
|
||||
file.height = this.height;
|
||||
resolve();
|
||||
};
|
||||
image.onerror = reject;
|
||||
image.src = src;
|
||||
};
|
||||
filereader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
const { dispatch, form } = this.props;
|
||||
e.preventDefault();
|
||||
form.validateFieldsAndScroll((err, values) => {// values 是form表单里的参数
|
||||
// 点击按钮后,将表单提交给后台
|
||||
dispatch({
|
||||
type: 'mymodel/submitFormData',
|
||||
payload: values,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { previewVisible, previewImage, imgList } = this.state; // 从 state 中拿数据
|
||||
const uploadButton = (
|
||||
<div>
|
||||
<Icon type="plus" />
|
||||
<div className="ant-upload-text">Upload</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="clearfix">
|
||||
<Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}>
|
||||
<FormItem label="图片图片" {...formItemLayout}>
|
||||
{getFieldDecorator('myImg')(
|
||||
<Upload
|
||||
action="//jsonplaceholder.typicode.com/posts/" // 这个是图片上传的接口请求,实际开发中,要替换成你自己的业务接口
|
||||
data={file => ({ // data里存放的是接口的请求参数
|
||||
param1: myParam1,
|
||||
param2: myParam2,
|
||||
photoCotent: file, // file 是当前正在上传的图片
|
||||
photoWidth: file.height, // 通过 handleBeforeUpload 获取 图片的宽高
|
||||
photoHeight: file.width,
|
||||
})}
|
||||
listType="picture-card"
|
||||
fileList={this.state.imgList}
|
||||
onPreview={this.handlePreview} // 点击图片缩略图,进行预览
|
||||
beforeUpload={this.handleBeforeUpload} // 上传之前,对图片的格式做校验,并获取图片的宽高
|
||||
onChange={this.handleChange} // 每次上传图片时,都会触发这个方法
|
||||
>
|
||||
{this.state.imgList.length >= 9 ? null : uploadButton}
|
||||
</Upload>
|
||||
)}
|
||||
</FormItem>
|
||||
</Form>
|
||||
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
|
||||
<img alt="example" style={{ width: '100%' }} src={previewImage} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PicturesWall;
|
||||
|
||||
```
|
||||
|
||||
## 上传后,点击图片预览,浏览器卡死的问题
|
||||
|
||||
依据上方的代码,通过 Antd 的 upload 组件将图片上传成功后,点击图片的缩略图,理应可以在当前页面弹出 Modal,预览图片。但实际的结果是,浏览器一定会卡死。
|
||||
|
||||
定位问题发现,原因竟然是:图片上传成功后, upload 会将其转为 base64编码。base64这个字符串太大了,点击图片预览的时候,浏览器在解析一大串字符串,然后就卡死了。详细过程描述如下。
|
||||
|
||||
上方代码中,我们可以把 handleChange(file, fileList)方法中的 `file`、以及 `fileList`打印出来看看。 `file`指的是当前正在上传的 单个 img,`fileList`是已上传的全部 img 列表。 当我上传完 两张图片后, 打印结果如下:
|
||||
|
||||
file的打印的结果如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"uid": "rc-upload-1551084269812-5",
|
||||
"width": 600,
|
||||
"height": 354,
|
||||
"lastModified": 1546701318000,
|
||||
"lastModifiedDate": "2019-01-05T15:15:18.000Z",
|
||||
"name": "e30e7b9680634b2c888c8bb513cc595d.jpg",
|
||||
"size": 31731,
|
||||
"type": "image/jpeg",
|
||||
"percent": 100,
|
||||
"originFileObj": {
|
||||
"uid": "rc-upload-1551084269812-5",
|
||||
"width": 600,
|
||||
"height": 354
|
||||
},
|
||||
"status": "done",
|
||||
"thumbUrl": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAHQ9qKKlbimcXrIH9o2vH/AC2T+ddPj98v+9RRWsuhnHdk0ar9qb5R0Pb6VPB/qh9aKKiRr0Irnt/vUDr+NFFJCRqWxJik5Pb+dLJ938aKK06mYSdKKKKBH//Z",
|
||||
"response": {
|
||||
"retCode": 0,
|
||||
"imgUrl": "http://qianguyihao.com/opfewfwj098902kpkpkkj976fe.jpg",
|
||||
"photoid": 271850
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
fileList 的打印结果:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"uid": "rc-upload-1551084269812-3",
|
||||
"width": 1000,
|
||||
"height": 667,
|
||||
"lastModified": 1501414799000,
|
||||
"lastModifiedDate": "2017-07-30T11:39:59.000Z",
|
||||
"name": "29381f30e924b89914e91b33.jpg",
|
||||
"size": 135204,
|
||||
"type": "image/jpeg",
|
||||
"percent": 100,
|
||||
"originFileObj": {
|
||||
"uid": "rc-upload-1551084269812-3",
|
||||
"width": 1000,
|
||||
"height": 667
|
||||
},
|
||||
"status": "done",
|
||||
"thumbUrl": "data:image/jpeg;base64,/E3ju1tlaK1fzJOnHQU3LsLV7HO6Zrk11MZJ7luT0A4FZuRagi9quvzQQ4iuEJ7ZpqTG4djDsPFl2Lg733f8C4q+YhQ8zoYfGSqoMmfwo5huLL0HjiyPDSYPvxRdC1XQvxeLrB8fvl/OnoLmL9vrdvvYS3NGFVe2YsASOh71JfQyrqV2mXLHOcccVSIYEnDyZO9XXB9KYH//Z",
|
||||
"response": {
|
||||
"retCode": 0,
|
||||
"msg": "success",
|
||||
"imgUrl": "http://qianguyihao.com/hfwpjouiurewnmbhepr689.jpg",
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "rc-upload-1551084269812-5",
|
||||
"width": 600,
|
||||
"height": 354,
|
||||
"lastModified": 1546701318000,
|
||||
"lastModifiedDate": "2019-01-05T15:15:18.000Z",
|
||||
"name": "e30e7b9680634b2c888c8bb513cc595d.jpg",
|
||||
"size": 31731,
|
||||
"type": "image/jpeg",
|
||||
"percent": 100,
|
||||
"originFileObj": {
|
||||
"uid": "rc-upload-1551084269812-5",
|
||||
"width": 600,
|
||||
"height": 354
|
||||
},
|
||||
"status": "done",
|
||||
"thumbUrl": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAHQ9qKKlbimcXrIH9o2vH/AC2T+ddPj98v+9RRWsuhnHdk0ar9qb5R0Pb6VPB/qh9aKKiRr0Irnt/vUDr+NFFJCRqWxJik5Pb+dLJ938aKK06mYSdKKKKBH//Z",
|
||||
"response": {
|
||||
"retCode": 0,
|
||||
"imgUrl": "http://qianguyihao.com/opfewfwj098902kpkpkkj976fe.jpg",
|
||||
"photoid": 271850
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
上方的json数据中,需要做几点解释:
|
||||
|
||||
(1)`response` 字段里面的数据,就是请求接口后,后台返回给前端的数据,里面包含了图片的url链接。
|
||||
|
||||
(2)`status` 字段里存放的是图片上传的实时状态,包括上传中、上传完成、上传失败。
|
||||
|
||||
(3)`thumbUrl`字段里面存放的是图片的base64编码。
|
||||
|
||||
这个base64编码非常非常长。当点击图片预览的时候,其实就是加载的 thumbUrl 这个字段里的资源,难怪浏览器会卡死。
|
||||
|
||||
**解决办法**:在 handleChange方法里,图片上传成功后,将 thumbUrl 字段里面的 base64 编码改为真实的图片url。代码实现如下:
|
||||
|
||||
```javascript
|
||||
handleChange = ({ file, fileList }) => {
|
||||
console.log(JSON.stringify(file)); // file 是当前正在上传的 单个 img
|
||||
console.log(JSON.stringify(fileList)); // fileList 是已上传的全部 img 列表
|
||||
|
||||
|
||||
// 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。
|
||||
// 图片上传成功后,fileList数组中的 thumbUrl 中保存的是图片的base64字符串,这种情况,导致的问题是:图片上传成功后,点击图片缩略图,浏览器会会卡死。而下面这行代码,可以解决该bug。
|
||||
fileList.forEach(imgItem => {
|
||||
if (imgItem && imgItem.status == 'done' && imgItem.response && imgItem.response.imgUrl) {
|
||||
imgItem.thumbUrl = imgItem.response.imgUrl;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
imgList: fileList,
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## 新需求:编辑现有页面
|
||||
|
||||
上面一段的代码中,我们是在新建的页面中,从零开始上传图片。
|
||||
|
||||
现在有个新的需求:如何编辑现有的页面呢?也就是说,现有的页面在初始化时,是默认有几张图片的。当我编辑这个页面时,可以对现有的图片做增删,也能增加新的图片。而且要保证:新建页面和编辑现有页面,是共用一套代码。
|
||||
|
||||
我看到upload 组件有提供 `defaultFileList` 的属性。我试了下,这个`defaultFileList` 的属性根本没法儿用。
|
||||
|
||||
那就只有手动实现了。我的model层代码,是用 redux 写的。整体的实现思路如下:(这个也是在真正在实战中用到的代码)
|
||||
|
||||
(1)PicturesWall.js:
|
||||
|
||||
```javascript
|
||||
/* eslint-disable */
|
||||
|
||||
import { Upload, Icon, Modal, Form } from 'antd';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
class PicturesWall extends PureComponent {
|
||||
state = {
|
||||
previewVisible: false,
|
||||
previewImage: '',
|
||||
};
|
||||
|
||||
// 页面初始化的时候,从接口拉取默认的图片数据
|
||||
componentDidMount() {
|
||||
const { dispatch } = this.props;
|
||||
dispatch({
|
||||
type: 'mymodel/getAllInfo',
|
||||
payload: { params: xxx },
|
||||
});
|
||||
}
|
||||
|
||||
handleChange = ({ file, fileList }) => {
|
||||
const { dispatch } = this.props;
|
||||
// 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。
|
||||
// 图片上传成功后,fileList数组中的 thumbUrl 中保存的是图片的base64字符串,这种情况,导致的问题是:图片上传成功后,点击图片缩略图,浏览器会会卡死。而下面这行代码,可以解决该bug。
|
||||
fileList.forEach(imgItem => {
|
||||
if (imgItem && imgItem.status == 'done' && imgItem.response && imgItem.response.imgUrl) {
|
||||
imgItem.thumbUrl = imgItem.response.imgUrl;
|
||||
}
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: 'mymodel/setImgList',
|
||||
payload: fileList,
|
||||
});
|
||||
};
|
||||
|
||||
handleCancel = () => this.setState({ previewVisible: false });
|
||||
|
||||
handlePreview = file => {
|
||||
this.setState({
|
||||
previewImage: file.url || file.thumbUrl,
|
||||
previewVisible: true,
|
||||
});
|
||||
};
|
||||
|
||||
// 参考链接:https://www.jianshu.com/p/f356f050b3c9
|
||||
handleBeforeUpload = file => {
|
||||
//限制图片 格式、size、分辨率
|
||||
const isJPG = file.type === 'image/jpeg';
|
||||
const isJPEG = file.type === 'image/jpeg';
|
||||
const isGIF = file.type === 'image/gif';
|
||||
const isPNG = file.type === 'image/png';
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
|
||||
if (!(isJPG || isJPEG || isGIF || isPNG)) {
|
||||
Modal.error({
|
||||
title: '只能上传JPG 、JPEG 、GIF、 PNG格式的图片~',
|
||||
});
|
||||
} else if (!isLt2M) {
|
||||
Modal.error({
|
||||
title: '超过2M限制,不允许上传~',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 参考链接:https://github.com/ant-design/ant-design/issues/8779
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!(isJPG || isJPEG || isGIF || isPNG)) {
|
||||
reject(file);
|
||||
} else {
|
||||
resolve(file && this.checkImageWH(file));
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
//返回一个 promise:检测通过则返回resolve;失败则返回reject,并阻止图片上传
|
||||
checkImageWH(file) {
|
||||
let self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
let filereader = new FileReader();
|
||||
filereader.onload = e => {
|
||||
let src = e.target.result;
|
||||
const image = new Image();
|
||||
image.onload = function() {
|
||||
// 获取图片的宽高,并存放到file对象中
|
||||
console.log('file width :' + this.width);
|
||||
console.log('file height :' + this.height);
|
||||
file.width = this.width;
|
||||
file.height = this.height;
|
||||
resolve();
|
||||
};
|
||||
image.onerror = reject;
|
||||
image.src = src;
|
||||
};
|
||||
filereader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
const { dispatch, form } = this.props;
|
||||
e.preventDefault();
|
||||
|
||||
const {
|
||||
mymodel: { imgList }, // 从props中拿默认的图片数据
|
||||
} = this.props;
|
||||
|
||||
form.validateFieldsAndScroll((err, values) => {
|
||||
// values 是form表单里的参数
|
||||
// 点击按钮后,将表单提交给后台
|
||||
|
||||
|
||||
// start 问题描述:当编辑现有页面时,如果针对已经存在的默认图片不做修改,则不会触发 upload 的 onChange方法。此时提交表单,表单里的 myImg 字段是空的。
|
||||
// 解决办法:如果发现存在默认图片,则追加到表单中
|
||||
if (!values.myImg) {
|
||||
|
||||
values.myImg = { fileList: [] };
|
||||
|
||||
values.myImg.fileList = imgList;
|
||||
}
|
||||
// end
|
||||
|
||||
dispatch({
|
||||
type: 'mymodel/submitFormData',
|
||||
payload: values,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { previewVisible, previewImage } = this.state; // 从 state 中拿数据
|
||||
|
||||
const {
|
||||
mymodel: { imgList }, // 从props中拿到的图片数据
|
||||
} = this.props;
|
||||
|
||||
const uploadButton = (
|
||||
<div>
|
||||
<Icon type="plus" />
|
||||
<div className="ant-upload-text">Upload</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="clearfix">
|
||||
<Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}>
|
||||
<FormItem label="图片上传" {...formItemLayout}>
|
||||
{getFieldDecorator('myImg')(
|
||||
<Upload
|
||||
action="//jsonplaceholder.typicode.com/posts/" // 这个是图片上传的接口请求,实际开发中,要替换成你自己的业务接口
|
||||
data={file => ({
|
||||
// data里存放的是接口的请求参数
|
||||
param1: myParam1,
|
||||
param2: myParam2,
|
||||
photoCotent: file, // file 是当前正在上传的图片
|
||||
photoWidth: file.height, // 通过 handleBeforeUpload 获取 图片的宽高
|
||||
photoHeight: file.width,
|
||||
})}
|
||||
listType="picture-card"
|
||||
fileList={imgList} // 改为从 props 里拿图片数据,而不是从 state
|
||||
onPreview={this.handlePreview} // 点击图片缩略图,进行预览
|
||||
beforeUpload={this.handleBeforeUpload} // 上传之前,对图片的格式做校验,并获取图片的宽高
|
||||
onChange={this.handleChange} // 每次上传图片时,都会触发这个方法
|
||||
>
|
||||
{this.state.imgList.length >= 9 ? null : uploadButton}
|
||||
</Upload>
|
||||
)}
|
||||
</FormItem>
|
||||
</Form>
|
||||
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
|
||||
<img alt="example" style={{ width: '100%' }} src={previewImage} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PicturesWall;
|
||||
|
||||
```
|
||||
|
||||
(2)mymodel.js:
|
||||
|
||||
```javascript
|
||||
/* eslint-disable */
|
||||
|
||||
import { routerRedux } from 'dva/router';
|
||||
import { message, Modal } from 'antd';
|
||||
import {
|
||||
getGoodsInfo,
|
||||
getAllGoods,
|
||||
} from '../services/api';
|
||||
import { trim, getCookie } from '../utils/utils';
|
||||
|
||||
export default {
|
||||
namespace: 'mymodel',
|
||||
|
||||
state: {
|
||||
form: {},
|
||||
list: [],
|
||||
listDetail: [],
|
||||
goodsList: [],
|
||||
goodsListDetail: [],
|
||||
pagination: {
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
current: 1,
|
||||
},
|
||||
imgList: [], //图片
|
||||
},
|
||||
subscriptions: {
|
||||
setup({ dispatch, history }) {
|
||||
history.listen(location => {
|
||||
if (location.pathname !== '/xx/xxx') return;
|
||||
if (!location.state || !location.state.xxxId) return;
|
||||
dispatch({
|
||||
type: 'fetch',
|
||||
payload: location.state,
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
effects: {
|
||||
// 接口。获取所有工厂店的列表 (步骤02)
|
||||
*getAllInfo({ payload }, { select, call, put }) {
|
||||
yield put({
|
||||
type: 'form',
|
||||
payload,
|
||||
});
|
||||
console.log('params:' + JSON.stringify(payload));
|
||||
|
||||
let params = {};
|
||||
params = payload;
|
||||
|
||||
const response = yield call(getGoodsInfo, params);
|
||||
|
||||
console.log('smyhvae response:' + JSON.stringify(response));
|
||||
if (response.error) return;
|
||||
yield put({
|
||||
type: 'allInfo',
|
||||
payload:
|
||||
(response.data &&
|
||||
response.data.map(item => ({
|
||||
xx1: item.yy1,
|
||||
xx2: item.yy2,
|
||||
}))) ||
|
||||
[],
|
||||
});
|
||||
|
||||
// response 里包含了接口返回给前端的默认图片数据
|
||||
if (response && response.data && response.data[0] && response.data[0].my_jpg) {
|
||||
let tempImgList = response.data[0].my_jpg.split(',');
|
||||
let imgList = [];
|
||||
|
||||
if (tempImgList.length > 0) {
|
||||
tempImgList.forEach(item => {
|
||||
imgList.push({
|
||||
uid: item,
|
||||
name: 'xxx.png',
|
||||
status: 'done',
|
||||
thumbUrl: item,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 通过 redux的方式 将 默认图片 传给 imgList
|
||||
console.log('smyhvae payload imgList:' + JSON.stringify(imgList));
|
||||
yield put({
|
||||
type: 'setImgList',
|
||||
payload: imgList,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
*setImgList({ payload }, { call, put }) {
|
||||
console.log('model setImgList');
|
||||
yield put({
|
||||
type: 'getImgList',
|
||||
payload,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
reducers: {
|
||||
allInfo(state, action) {
|
||||
return {
|
||||
...state,
|
||||
list: action.payload,
|
||||
};
|
||||
},
|
||||
getImgList(state, action) {
|
||||
return {
|
||||
...state,
|
||||
imgList: action.payload,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
上面的代码,可以规避 upload 组件的一些bug;而且可以在上传前,通过校验图片的尺寸、大小等,如果不满足条件,则弹出modal弹窗,阻止上传。
|
||||
|
||||
大功告成。本文感谢 ld 同学的支持。
|
||||
|
||||
## 其他问题
|
||||
|
||||
- [beforeUpload返回false后,文件仍然为上传中的状态](https://github.com/ant-design/ant-design/issues/8779)
|
||||
|
||||
## 最后一段
|
||||
|
||||
有人说,前端开发,连卖菜的都会。可如果真的遇到技术难题,还是得找个靠谱的前端同学才行。这不,来看看前端码农日常:
|
||||
|
||||

|
||||
|
||||
|
||||
61
12-React基础/10-React Navive初识.md
Normal file
61
12-React基础/10-React Navive初识.md
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
|
||||
## 搭建开发环境
|
||||
|
||||
官方文档:<https://reactnative.cn/docs/getting-started.html>
|
||||
|
||||
### 安装Node、homebrew、Watchman
|
||||
|
||||
安装 homebrew:
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
安装 watchman:
|
||||
|
||||
```
|
||||
brew install watchman
|
||||
```
|
||||
|
||||
|
||||
Watchman则是由 Facebook 提供的监视文件系统变更的工具。安装此工具可以提高开发时的性能(packager 可以快速捕捉文件的变化从而实现实时刷新)。
|
||||
|
||||
|
||||
|
||||
### 安装 React Native 的命令行工具(react-native-cli)
|
||||
|
||||
安装 react-native-cli:
|
||||
|
||||
```
|
||||
npm install -g react-native-cli
|
||||
```
|
||||
|
||||
|
||||
React Native 的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务。
|
||||
|
||||
### 创建新项目
|
||||
|
||||
```
|
||||
react-native init MyApp --version 0.44.3
|
||||
```
|
||||
|
||||
### 编译并运行 React Native 应用
|
||||
|
||||
在 ios 模拟器上运行:
|
||||
|
||||
```
|
||||
react-native run-ios
|
||||
```
|
||||
|
||||
## 调试
|
||||
|
||||
官网文档:<https://reactnative.cn/docs/debugging.html>
|
||||
|
||||
|
||||
### 访问 App 内的开发菜单
|
||||
|
||||
如果是在 iOS 模拟器中运行,还可以按下`Command + D`快捷键,Android 模拟器对应的则是Command⌘ + M(windows 上可能是 F1 或者 F2),或是直接在命令行中运行adb shell input keyevent 82来发送菜单键命令。
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user