refactor: 目录结构调整

This commit is contained in:
qianguyihao
2020-04-19 18:24:43 +08:00
parent 8e979bff7d
commit 6fbb5659e4
106 changed files with 27 additions and 18 deletions

View 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。
- keykey这个属性可以把 页面上的 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-08React项目已经有 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`)。
扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:
![](http://img.smyhvae.com/20160401_01.jpg)

View File

@@ -0,0 +1,529 @@
## JSX介绍
### JSX的引入
如果直接让用户通过 JS 代码手动创建DOM元素肯定是非常麻烦的。
于是React 官方就提出了一套 JSX 语法规范,能够让我们在 JS 文件中,书写类似于 HTML 那样的代码快速定义虚拟DOM结构。
### JSX的全称
JSXJavaScript 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 创建出来的无状态组件。(使用无状态组件的小小好处: 由于剔除了组件的生命周期,所以,运行速度会相对快一点点)。

View 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()

View 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` 来举个例子。
1index.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>
```
2main.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做事件绑定
代码举例:
1index.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>
```
2main.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 提供的方法,做事件绑定
代码举例:
1index.html和 2main.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 值 是奇数则不更新页面。最终实现的的页面效果24681012....
// 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 值 是奇数则不更新页面。最终实现的的页面效果24681012....
// 经过打印测试发现:在 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。
代码举例:
1index.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>
```
2main.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")
);
```
3TestReceiveProps.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()函数 的注释部分。

View 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
});
};
}
```

View 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>
```
2main.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")
);
```
3components/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]()

View File

@@ -0,0 +1,253 @@
## React路由的使用
使用React路由之前我们需要先安装 `react-router-dom`这个包。比如:
```
yarn add react-router-dom
```
代码举例:
1index.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>
```
2main.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"));
```
3app.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>&nbsp;&nbsp;
<Link to="/movie">电影</Link>&nbsp;&nbsp;
<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>
);
}
}
```
4ReactDemo/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>;
}
}
```
5ReactDemo/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>;
}
}
```
6ReactDemo/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>&nbsp;&nbsp;
<Route path="/movie/:type/:id" component={Movie} exact/>
```
### 获取路由参数
继续修改上面的代码。如果我想在 Movie 组件中显示路由中的参数,怎么做呢?
我们可以通过 `props.match.params`获取路由中的参数。举例做法如下:
app.jsx中的匹配规则如下
```html
<Link to="/movie/top100/5">电影</Link>&nbsp;&nbsp;
<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
## 参考链接

View 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
```
### 代码示例
我们需要什么组件,就导入该组件即可。
1index.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>
```
2main.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>

View 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上传中
![](http://img.smyhvae.com/20190302_1335.png)
2上传成功
![](http://img.smyhvae.com/20190302_1336.png)
3图片预览
![](http://img.smyhvae.com/20190302_1331.png)
按照官方提供的实例,特此整理出项目开发中的完整写法,亲测有效。代码如下:
```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 写的。整体的实现思路如下:(这个也是在真正在实战中用到的代码)
1PicturesWall.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;
```
2mymodel.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)
## 最后一段
有人说,前端开发,连卖菜的都会。可如果真的遇到技术难题,还是得找个靠谱的前端同学才行。这不,来看看前端码农日常:
![](http://img.smyhvae.com/20190302_1339.png)

View 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⌘ + Mwindows 上可能是 F1 或者 F2或是直接在命令行中运行adb shell input keyevent 82来发送菜单键命令。