0%

一、Mobx 解决的问题

传统 React 使用的数据管理库为 Redux。Redux 要解决的问题是统一数据流,数据流完全可控并可追踪。要实现该目标,便需要进行相关的约束。Redux 由此引出了 dispatch action reducer 等概念,对 state 的概念进行强约束。然而对于一些项目来说,太过强,便失去了灵活性。Mobx 便是来填补此空缺的。

这里对 Redux 和 Mobx 进行简单的对比:

  1. Redux 的编程范式是函数式的而 Mobx 是面向对象的;

  2. 因此数据上来说 Redux 理想的是 immutable 的,每次都返回一个新的数据,而 Mobx 从始至终都是一份引用。因此 Redux 是支持数据回溯的;

  3. 然而和 Redux 相比,使用 Mobx 的组件可以做到精确更新,这一点得益于 Mobx 的 observable;对应的,Redux 是用 dispath 进行广播,通过 Provider 和 connect 来比对前后差别控制更新粒度,有时需要自己写 SCU;Mobx 更加精细一点。

二、 Mobx 核心概念

image

Mobx 的核心原理是通过 action 触发 state 的变化,进而触发 state 的衍生对象(computed value & Reactions)。

State

在 Mobx 中,State 就对应业务的最原始状态,通过 observable 方法,可以使这些状态变得可观察。

通常支持被 observable 的类型有三个,分别是 Object, Array, Map;对于原始类型,可以使用 Obserable.box。

值得注意的一点是,当某一数据被 observable 包装后,他返回的其实是被 observable 包装后的类型。

1
2
3
4
5
6

const Mobx = require("mobx");
const { observable, autorun } = Mobx;
const obArray = observable([1, 2, 3]);
console.log("ob is Array:", Array.isArray(obArray));
console.log("ob:", obArray);

控制台输出为:

1
2
3

ob is Array: false
ob: ObservableArray {}

对于该问题,解决方法也很简单,可以通过 Mobx 原始提供的 observable.toJS()转换成 JS 再判断,或者直接使用 Mobx 原生提供的 APIisObservableArray 进行判断。

computed

Mobx 中 state 的设计原则和 redux 有一点是相同的,那就是尽可能保证 state 足够小,足够原子。这样设计的原则不言而喻,无论是维护性还是性能。那么对于依赖 state 的数据而衍生出的数据,可以使用 computed。

简而言之,你有一个值,该值的结果依赖于 state,并且该值也需要被 observable,那么就使用 computed。

通常应该尽可能的使用计算属性,并且由于其函数式的特点,可以最大化优化性能。如果计算属性依赖的 state 没改变,或者该计算值没有被其他计算值或响应(reaction)使用,computed 便不会运行。在这种情况下,computed 处于暂停状态,此时若该计算属性不再被 observable。那么其便会被 Mobx 垃圾回收。

简单介绍 computed 的一个使用场景

假如你观察了一个数组,你想根据数组的长度变化作出反应,在不使用 computed 时代码是这样的

1
2
3
4
5
6
7
8
9
10

const Mobx = require("mobx");
const { observable, autorun, computed } = Mobx;
var numbers = observable([1, 2, 3]);
autorun(() => console.log(numbers.length));
// 输出 '3'
numbers.push(4);
// 输出 '4'
numbers[0] = 0;
// 输出 '4'

最后一行其实只是改了数组中的一个值,但是也触发了 autorun 的执行。此时如果用 computed 便会解决该问题。

1
2
3
4
5
6
7
8
9
10

const Mobx = require("mobx");
const { observable, autorun, computed } = Mobx;
var numbers = observable([1, 2, 3]);
var sum = computed(() => numbers.length);
autorun(() => console.log(sum.get()));
// 输出 '3'
numbers.push(4);
// 输出 '4'
numbers[0] = 1;

autorun

另一个响应 state 的 api 便是 autorun。和 computed 类似,每当依赖的值改变时,其都会改变。不同的是,autorun 没有了 computed 的优化(当然,依赖值未改变的情况下也不会重新运行,但不会被自动回收)。因此在使用场景来说,autorun 通常用来执行一些有副作用的。例如打印日志,更新 UI 等等。

action

在 redux 中,唯一可以更改 state 的途径便是 dispatch 一个 action。这种约束性带来的一个好处是可维护性。整个 state 只要改变必定是通过 action 触发的,对此只要找到 reducer 中对应的 action 便能找到影响数据改变的原因。强约束性是好的,但是 Redux 要达到约束性的目的,似乎要写许多样板代码,虽说有许多库都在解决该问题,然而 Mobx 从根本上来说会更加优雅。

首先 Mobx 并不强制所有 state 的改变必须通过 action 来改变,这主要适用于一些较小的项目。对于较大型的,需要多人合作的项目来说,可以使用 Mobx 提供的 api configure 来强制。

1
Mobx.configure({enforceActions: true})

其原理也很简单

1
2
3
4
5
6
7

function configure(options){
    if (options.enforceActions !== undefined) {
        globalState.enforceActions = !!options.enforceActions
        globalState.allowStateChanges = !options.enforceActions
    }
}

通过改变全局的 strictMode 以及 allowStateChanges 属性的方式来实现强制使用 action。

三、Mobx 异步处理

和 Redux 不同的是,Mobx 在异步处理上并不复杂,不需要引入额外的类似 redux-thunk、redux-saga 这样的库。

唯一需要注意的是,在严格模式下,对于异步 action 里的回调,若该回调也要修改 observable 的值,那么

该回调也需要绑定 action。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

const Mobx = require("mobx");
Mobx.configure({ enforceActions: true });
const { observable, autorun, computed, extendObservable, action } = Mobx;
class Store {
  @observable a = 123;

  @action
  changeA() {
    this.a = 0;
    setTimeout(this.changeB, 1000);
  }
  @action.bound
  changeB() {
    this.a = 1000;
  }
}
var s = new Store();
autorun(() => console.log(s.a));
s.changeA();

这里用了 action.bound 语法糖,目的是为了解决 javascript 作用域问题。

另外一种更简单的写法是直接包装 action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Mobx = require("mobx");
Mobx.configure({ enforceActions: true });
const { observable, autorun, computed, extendObservable, action } = Mobx;
class Store {
  @observable a = 123;
  @action
  changeA() {
    this.a = 0;
    setTimeout(action('changeB',()=>{
      this.a = 1000;
    }), 1000);
  }
}
var s = new Store();
autorun(() => console.log(s.a));
s.changeA();

如果不想到处写 action,可以使用 Mobx 提供的工具函数 runInAction 来简化操作。

1
2
3
4
5
6
7
8
9
10
11
12
...
 @action
  changeA() {
    this.a = 0;
    setTimeout(
      runInAction(() => {
        this.a = 1000;
      }),
      1000
    );
  }
...

通过该工具函数,可以将所有对 observable 值的操作放在一个回调里,而不是命名各种各样的 action。

最后,Mobx 提供的一个工具函数,其原理 redux-saga,使用 ES6 的 generator 来实现异步操作,可以彻底摆脱 action 的干扰。

1
2
3
4
5
6
@asyncAction
  changeA() {
    this.a = 0;
    const data = yield Promise.resolve(1)
    this.a = data;
  }

四、Mobx 原理分析

autorun

Mobx 的核心就是通过 observable 观察某一个变量,当该变量产生变化时,对应的 autorun 内的回调函数就会发生变化。

1
2
3
4
5
6
7
8
const Mobx = require("mobx");
const { observable, autorun } = Mobx;
const ob = observable({ a: 1, b: 1 });
autorun(() => {
  console.log("ob.b:", ob.b);
});

ob.b = 2;

执行该代码会发现,log 了两遍 ob.b 的值。其实从这个就能猜到,Mobx 是通过代理变量的 getter 和 setter 来实现的变量更新功能。首先先代理变量的 getter 函数,然后通过预执行一遍 autorun 中回调,从而触发 getter 函数,来实现观察值的收集,依次来代理 setter。之后只要 setter 触发便执行收集好的回调就 ok 了。
具体源码如下:

1
2
3
4
5
6
7
8
function autorun(view, opts){
reaction = new Reaction(name, function () {
this.track(reactionRunner);
}, opts.onError);
function reactionRunner() {
view(reaction);
}
}

autorun 的核心就是这一段,这里 view 就是 autorun 里的回调函数。具体到 track 函数,比较关键到代码是:

1
2
3
Reaction.prototype.track = function (fn) {
var result = trackDerivedFunction(this, fn, undefined);
}

trackDerivedFunction 函数中会执行 autorun 里的回调函数,紧接着会触发 observable 中代理的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function generateObservablePropConfig(propName) {
return (observablePropertyConfigs[propName] ||
(observablePropertyConfigs[propName] = {
configurable: true,
enumerable: true,
get: function () {
return this.$mobx.read(this, propName);
},
set: function (v) {
this.$mobx.write(this, propName, v);
}
}));
}

在 get 中会将回调与其绑定,之后更改了 observable 中的值时,都会触发这里的 set,然后随即触发绑定的函数。

五、Mobx 的一些坑

通过 autorun 的实现原理可以发现,会出现很多我们想象中应该触发,但是没有触发的场景,例如:

  1. 无法收集新增的属性
1
2
3
4
5
6
7
8
9
const Mobx = require("mobx");
const { observable, autorun } = Mobx;
let ob = observable({ a: 1, b: 1 });
autorun(() => {
if(ob.c){
console.log("ob.c:", ob.c);
}
});
ob.c = 1

对于该问题,可以通过 extendObservable(target, props)方法来实现。

1
2
3
4
5
6
7
8
9
10
const Mobx = require("mobx");
const { observable, autorun, computed, extendObservable } = Mobx;
var numbers = observable({ a: 1, b: 2 });
extendObservable(numbers, { c: 1 });
autorun(() => console.log(numbers.c));
numbers.c = 3;

// 1

// 3

extendObservable 该 API 会可以为对象新增加 observal 属性。

当然,如果你对变量的 entry 增删非常关心,应该使用 Map 数据结构而不是 Object。

  1. 回调函数若依赖外部环境,则无法进行收集
1
2
3
4
5
6
7
8
9
10
11
const Mobx = require("mobx");
const { observable, autorun } = Mobx;
let ob = observable({ a: 1, b: 1 });
let x = 0;
autorun(() => {
if(x == 1){
console.log("ob.c:", ob.b);
}
});
x = 1;
ob.b = 2;

很好理解,autorun 的回调函数在预执行的时候无法到达 ob.b 那一行代码,所以收集不到。

1.路径匹配问题:

In Nuxt.js, the path match is as follows:

1
@import url('~assets/css/style.css') //Error

This path matching is an error, and writing it like this is possible:

1
@import url('~/assets/css/style.css') //success

也就是说,在最新版本更新中,官方修复了路径匹配问题:

而官方推荐使用~/assets匹配路径,而不是使用在中文文档中的~assets去匹配路径。

而在中文文档中,也并未见修复及更改此问题。

2.按需引入(UI 框架等等)

例如使用 UI 框架:element-ui

我找了很多相关文章,并没有详细说明该如何引入。所以我要拿出来将他说明:

先来看下,如果不按需引入 vendor.js 的体积大小为:

image

第一步,下载依赖:

1
2
3
4
5
6
7
# 先下载element-ui

npm install element-ui --save

# 如果使用按需引入,必须安装babel-plugin-component(官网有需要下载说明,此插件根据官网规则不同,安装插件不同)

npm install babel-plugin-component --save-dev

安装好以后,按照 nuxt.js 中的规则,你需要在 plugins/ 目录下创建相应的插件文件

在文件根目录创建(或已经存在)plugins/目录,创建名为:element-ui.js 的文件,内容如下:

1
2
3
4
5
6
7
import Vue from 'vue'

import { Button } from 'element-ui' //引入Button按钮

export default ()=>{
Vue.use(Button)
}

第二步,引入插件

在 nuxt.config.js 中,添加配置为:plugins

1
2
3
4
5
6
css:[
'element-ui/lib/theme-chalk/index.css'
],
plugins:[
'~/plugins/element-ui'
]

默认为:开启 SSR,采用服务端渲染,也可以手动配置关闭 SSR,配置为:

1
2
3
4
5
6
7
8
9
css:[
'element-ui/lib/theme-chalk/index.css'
],
plugins:[
{
src:'~/plugins/element-ui',
ssr:false //关闭ssr
}
]

第三步,配置 babel 选项

在 nuxt.config.js 中,配置在 build 选项中,规则为官网规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

build: {
babel:{ //配置按需引入规则
"plugins":[
[
"component",
{
"libraryName":"element-ui",
"styleLibraryName":"theme-chalk"
}
]
]
},
/*
** Run ESLINT on save
*/
extend (config, ctx) {
if (ctx.isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
}
}

此时,我们在观察打包以后文件体积大小,如图:
image

此时,我们成功完成了按需引入配置。

3.初始化脚手架的选择:

官网提供的初始化脚手架为:

1
2
3
# 基本的Nuxt.js项目模板

vue init nuxt/starter template

而其实,官方也提供了更多的模板以便于我们使用,而我在中文文档并未发现有说明:

  • nuxt/starter 基本的 Nuxt.js 项目模板
  • nuxt/express Nuxt.js + Express
  • nuxt/koa Nuxt.js + Koa2
  • nuxt/adonuxt Nuxt.js + AdonisJS
  • nuxt/micro Nuxt.js + Micro
  • nuxt/nuxtent 适用于内容较重网站的 Nuxt.js + Nuxtent 模块

而我们使用基础的模板进行初始化项目,部署方式为:

第一步,打包:
在执行 npm run build 的时候,nuxt 会自动打包

第二步,选择要部署的文件:

  • .nuxt 文件夹
  • package.json 文件
  • nuxt.config.js 文件(如果你部署一些 proxy,则需要上传这个文件,个人建议把它传上去)

    第三步,启动你的 nuxt (重要)

使用 pm2 启动你的 nuxt.js

1
pm2 start npm --name "demo" -- run start

在这里,我发现个问题,如果你使用 window server 服务器,在使用 pm2 启动时候,会出现错误,错误如下:

image

如果在 Linux 服务器下启动,同样的命令,同样的执行,则不会出现错误:
这里采用 Linux CentOS 7

image

所以,个人建议,在采用初始化模板的时候,请选用 express 或者 koa 进行初始化,理由如下:

1.采用基础模板初始化,观察 package.json 的启动方式如下:

1
2
3
4
5
6
7
8
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
"precommit": "npm run lint"
}

2.采用 express/koa 初始化模板,观察 package.json 的启动方式如下:

1
2
3
4
5
6
7
"scripts": {
"dev": "backpack dev",
"build": "nuxt build && backpack build",
"start": "cross-env NODE_ENV=production node build/main.js",
"precommit": "npm run lint",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
}

在 start 中,对比下,个人觉得 express/koa 更灵活一些,它直接启动了 build/main.js 文件,更能直观的启动方式,而关键在于,也可以在 windows server 下运行起来。

注意事项:如果采用 express/ koa 的模板初始化,服务器部署的时候,同时要上传 build/目录!!!

4.插件中获取 vue 绑定

我们需要在 axios 的插件中配置 Loading 加载效果,例如使用 element-ui 框架作为示例:

1.创建插件
在文件根目录创建(或已经存在)plugins/目录,创建名为:axios.js 的文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Vue from 'vue'

var vm = new Vue({}) //获取vue实例

export default function ({ $axios, redirect }) {

$axios.onRequest(config => {
if (process.browser) { //判断是否为客户端(必须)
vm.$loading();
}
})

$axios.onResponse(response=>{
if (process.browser) { //判断是否为客户端(必须)
let load = vm.$loading();
load.close();
}
})

$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
})
}

如官方所说,并不需要像原生 axios一样,去 return 一个 config 出来。

2.配置 nuxt.config.js 文件
在 plugins 选项添加:

plugins:[‘~/plugins/axios’]

添加 modules 选项并添加如下示例:

modules:[‘@nuxtjs/axios’]

配置防止多次打包:

在 build 选项中(nuxt.config.js 会默认配置)添加 vendor 配置项:

1
2
3
build:{
vendor:['axios']
}

这样就可以调用 loading 加载方法,并且愉快的使用了。

(当然还有其他的方法去调用 vue 实例,每个人习惯不同,使用方式不同。)

5.Nuxt.js 中配置代理解决跨域

我们知道在 vue-cli 中配置代理很方便,只需要在 config/目录下的 index.js 中找到 proxyTable 添加即可,而在 nuxt 中同样需要修改 nuxt.config.js 配置文件。

1.原始配置代理方式
使用@nuxtjs/axios 和@nuxtjs/proxy 进行代理解决跨域

1).下载插件

1
2
3
# 下载插件

npm install @nuxtjs/axios @nuxtjs/proxy --save

2).配置插件

在 nuxt.config.js 添加配置项:modules 和 proxy。

1
2
3
4
5
6
7
8
9
10
11
export default = {

modules:[
'@nuxtjs/axios',
'@nuxtjs/proxy'
],
proxy:[
['/json.html',{target:'http://www.xxxx.com'}] //注意这也是一个数组
]

}

按照上面的方式已经完成了代理,可以进行跨域请求了。

2.第二种方式的代理配置

1).下载插件

这次只需要下载@nuxtjs/axios 插件就可以。

1
2
3
# 下载插件

npm install @nuxtjs/axios --save

2).配置插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {

modules: [
'@nuxtjs/axios',
],
axios: {
proxy:true
},
proxy:{
'/api': 'http://api.example.com',
'/api2': 'http://api.another-website.com'
}

}

特别注意:此时,axios 选项为对象(object),proxy 选项为对象(object)。

6.@nuxtjs/axios 的配置项

pathRewrite 选项(重写地址)

如果配置 pathRewrite 选项,可以采用第二种写法如下:

1
2
3
4
5
proxy: {

'/api/': { target: 'http://api.example.com', pathRewrite: {'^/api/': ''} }

}

/api/将被添加到 API 端点的所有请求中。可以使用 pathRewrite 选项删除。

因为在 ajax 的 url 中加了前缀 /api,而原本的接口是没有这个前缀的。

所以需要通过 pathRewrite 来重写地址,将前缀 /api 转为 /或者是’’。

如果本身的接口地址就有 /api 这种通用前缀,就可以把 pathRewrite 删掉。

retry 选项(自动拦截失败请求)

可以在 axios 选项中,配置 retry 配置项,自动拦截失败请求,默认为 3 次。

1
2
3
axios: {
retry: { retries: 3 }
}

progress 选项(发出请求时显示加载栏)

与 Nuxt.js 进度条集成,在发出请求时显示加载栏。(仅在浏览器上,当加载栏可用时。)

您还可以使用 progress 配置为每个请求禁用进度条。

1
this.$axios.$get('URL', { progress: false })

baseURL 选项(服务器端默认请求地址)

在服务器端使用和预先创建请求的基本 URL。

browserBaseURL 选项(客户端默认请求地址)

在客户端使用和预先创建请求的基本 URL。

一、简介

Next.js 是一个用于 React 应用的极简的服务端渲染框架。框架中集成了 Webpack,Babel 等一系列 React 相关的工具并进行了默认的配置。因此省去了复杂的配置过程,实现了一键搭建开发环境和打包构建。同时提供了自定义配置接口,可以在默认配置的基础上对工具进行自定义配置,满足个性化需求。

二、基本用法

安装

使用 npm 安装: npm install next –save

为了方便的使用 next 提供的命令,把命令写在 package.json 文件的 scripts 中:

1
2
3
4
5
6
7
8
{
"scripts": {
"dev": "next", // 运行开发服务器,并监控源代码,具备hod reload功能
"build": "next build", // 以生产模式打包代码
"start": "next start" // 启动Next服务器,可以自定义服务器和端口
"init": "next init" // 初始化项目,创建基础的文件夹和index页面文件
}
}

之后,在项目的根目录下创建 pages 文件夹和 static 文件夹,分别用来放对应的页面资源和静态资源。

Note:也可以使用 npm run init 命令自动生成。

运行

如果使用 npm run init 命令的话,现在 pages 文件夹下已经有了 index.js 文件,如果是手动创建 pages 文件夹的话,现在在该文件下创建一个 index.js 文件,内容为:

1
export default () => <p>Hello, world</p>

接着执行 npm run dev 命令并在浏览器中打开 http://localhost:3000。

现在,就得到了一个采用服务端渲染的极简 React 应用,这个应用还实现了自动代码分割,保证每个页面只会加载自身的依赖,不会有依赖冗余。

Next 的核心就是 pages 和 static 文件夹。其中 pages 文件夹用于存放每个页面的顶层组件,static 用于存放项目中的静态资源。

Next 会将 pages 中的文件结构自动映射为对应的路由结构,例如现在该文件夹下有两个文件:pages/index.js 和 pages/about.js。则对应的路由分别为/和/about。并且支持多级目录,例如 page/foo/bar.js 对应的路由为/foo/bar。

static 文件夹用来存放静态文件,例如现在有一个图片文件 static/image.png,使用的时候引用/static/image.png 就可以了:

1
2
3
export default () => (
<img src="/static/image.png" />
)

打包完成后,Next 会在项目根目录生成一个.next 文件夹,其中的两个文件夹 dist 和 bundles,dist 文件夹中存放着编译后的源代码,用于服务端渲染。bunldes 文件夹中存放着 pages 中每个页面打包后的整体代码的 JSON 格式。在应用的初始页面,会使用 dist 文件夹中的代码进行服务端渲染,而其他使用路由到达的页面,则将 bundles 文件夹中的对应 JSON 格式的代码返回客户端执行渲染。

Next 的出现大大简化了 React 应用开发的配置和构建工作,使开发者能够专注于组件的开发,而不需要在 Webpack,Babel 等工具上花费过多的精力。基于简单的文件系统,就可以创建包含路由功能和服务端渲染的 React 应用。需要注意的是:创建的应用中只有初始页面采用服务端渲染,其他通过路由操作到达的页面均为客户端渲染。

组件

Next 对 React 组件的 getInitialProps 生命周期方法做了改造,传入一个上下文对象,该对象在服务端渲染和客户端渲染时,具有不同的属性:

  • req: HTTP 请求对象(服务端渲染独有)
  • res: HTTP 响应对象(服务端渲染独有)
  • pathname: URL 中的路径部分
  • query:URL 中的查询字符串部分解析出的对象
  • err:错误对象,如果在渲染时发生了错误
  • xhr:XMLHttpRequest 对象(客户端渲染独有)

因此,可以在组件的 getInitialProps 方法中处理上下文对象,控制传入组件的 props 数据。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react'
export default class extends React.Component {
static async getInitialProps ({ req }) {
return req
? { userAgent: req.headers['user-agent'] }
: { userAgent: navigator.userAgent }
}
render () {
return <div>
Hello World {this.props.userAgent}
</div>
}
}

上面的例子根据是否有 req 对象来判断是服务端渲染还是客户端渲染,然后采用对应的方式取得用户代码数据并传入组件的 props 中。

获取数据

组件的 getInitialProps 还可以用来获取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { Component } from 'react';
import 'isomorphic-fetch';

export default class extends Component {
static async getInitialProps() {
const res = await fetch('https://api.github.com/repos/zeit/next.js');
const json = await res.json();
return {
stars: json.stargazers_count
};

}

render() {
return <div>{this.props.stars}</div>
}
}

需要注意的一点是,getInitialProps 方法执行完毕之后,才会执行组件的 render 方法。这也就导致了如果网络状况不佳的情况下,会出现长时间的等待。并且只有每个页面的顶层组件的 getInitialProps 会被执行,所以想在子组件中获取数据的话只能在其他生命周期函数例如 componentDidMount 配合组件的 state 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default class extends Component {
constructor(props) {
super(props);
this.state = {
stars: 0
}
}

async componentDidMount() {
const res = await fetch('https://api.github.com/repos/zeit/next.js');
const json = await res.json();
this.setState({
stars: json.stargazers_count
});
}

render() {
return <div>{this.state.stars}</div>
}
}

三、CSS

NEXT 组件中声明 CSS,目前主要有两种方式:

  1. 内嵌 CSS
  2. CSS-in-JS

内嵌(Built-in)CSS

Next 采用的内嵌 CSS 方案是 styled-jsx 库,也是 Next 所推荐的 CSS 声明方式。优点是具有组件级的独立作用域,避免了样式污染问题。并且支持完整的 CSS 功能,如:hover 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react'

export default () => (
<div>
Hello world
<p>scoped!</p>
<style jsx>{`
p {
color: blue;
}
div {
background: red;
}
div:hover {
background: blue;
}
@media (max-width: 600px) {
div {
background: blue;
}
}
`}</style>
</div>
)

CSS-in-JS

Next 支持多种 CSS-in-JS 方案,例如基本的在组件 style 属性中写样式:

1
2
3
4
5
6
7
8
import React from 'react'

export default () => (
<div style={{color: red}}>
Hello world
</div>
)

还有其他的 CSS-in-JS 库,可以根据自己的需要和喜好灵活选择。

四、路由系统

Next 中提供了一个组件,用来实现路由功能。例如,我们的应用有两个页面:pages/index.js 和 pages/about.js,想要实现页面跳转,只需要:

1
2
3
4
5
// pages/index.js
import Link from 'next/link'
export default () => (
<div>Click <Link href="/about"><a>here</a></Link> to read more</div>
)
1
2
3
4
// pages/about.js
export default () => (
<p>Welcome to About!</p>
)
组件的工作流程和浏览器很相似:
  1. 获取新的组件
  2. 如果新组件定义了 getInitialProps,则获取数据,如果发生错误,则渲染_error.js
  3. 步骤 1,2 完成之后,执行 pushState 并渲染新组件
    每个顶层组件中还会传入一个 url 对象,提供了几个路由相关的方法:
  • pathname:String-当前 URL 不包括查询字符串的 path 部分
  • query:Object-当前 URL 中查询字符串解析成的对象
  • back-后退
  • push(url, as=url)-使用传入的 url(字符串)执行 pushState 操作
  • replace(url, as=url)-使用传入的 url(字符串)执行 replaceState 操作 注意:push 和 replace 方法中的第二个参数 as 为可选项,只有在服务端配置了自定义路由才有作用。

Router 对象

除了使用组件之外,Next 还提供了一个 Router 对象满足命令式写法的需要:

1
2
3
4
5
import Router from 'next/router'

export default () => (
<div>Click <span onClick={() => Router.push('/about')}>here</span> to read more</div>
)

与 url 对象相比,Router 对象多了一个 route 属性,值为当前的路由。 需要注意的是,Router 对象中的属性和方法仅可以在客户端部分使用,服务端渲染的页面无法使用,否则会报错。

路由事件

Router 对象还提供了三个路由事件方法:

  • routeChangeStart(url) - 路由变化开始时触发
  • routeChangeComplete(url) - 路由变化完成时触发
  • routeChangeError(err, url) - 路由变化发生错误时触发 如果使用 Router.push(url, as)或相似的方法并传入了 as 参数,则路由事件方法中的 url 参数值为 as 的值,否则,url 参数的值是路由跳转目标的 URL

注意:与 Router 对象中其他的属性和方法不同的是,这三个路由事件方法可以在服务端渲染的页面使用。

监听路由变化:

1
2
3
Router.onRouteChangeStart = (url) => {
console.log('App is changing to: ', url)
}

取消监听:

1
Router.onRouteChangeStart = null;

如果路由加载取消了(连续快速点击两个链接),就会触发 routeChangeError 的回调,传入的 err 参数中将包含一个 cancelled 属性,值为 true。

1
2
3
4
5
Router.onRouteChangeError = (err, url) => {
if (err.cancelled) {
console.log(`Route to ${url} was cancelled!`)
}
}

五、预获取页面

Next 提供了一个基于 ServiceWorker 实现的,具有预获取页面功能的模块:next/prefetch。 使用预获取功能,可以使 APP 预加载那些可能到达的页面,提升网站的使用体验和性能。当然,前提是你的浏览器必须支持 ServiceWorker。并且预获取功能只支持应用内的页面,不支持外部链接。

组件

next/prefetch 模块也提供了一个具有预获取功能的组件,代替路由系统中的组件,使用方法一致:

1
2
3
4
5
6
7
8
9
10
11
import Link from 'next/prefetch'

export default () => (
<nav>
<ul>
<li><Link href='/'><a>Home</a></Link></li>
<li><Link href='/about'><a>About</a></Link></li>
<li><Link href='/contact'><a>Contact</a></Link></li>
</ul>
</nav>
)

此外预获取功能可以精确控制到每个标签,使用 prefetch 属性来控制开关:

1
<Link href='/contact' prefetch={false}><a>Home</a></Link>

prefetch 方法

和路由器一样,预获取模块也提供了一个 prefetch 方法,用来方便命令式的写法:

1
2
3
4
5
6
7
8
9
10
11
12
import { prefetch } from 'next/prefetch'
export default ({ url }) => (
<div>
<a onClick={ () => setTimeout(() => url.pushTo('/dynamic'), 100) }>
100ms后执行路由跳转
</a>
{
预获取页面
prefetch('/dynamic')
}
</div>
)

自定义配置

如果默认的配置无法满足需要的话,Next 还提供了诸多的自定义配置接口,可以根据自己的需求灵活配置。

自定义服务器和路由

默认的服务器和路由系统可能无法满足需要,比如,我需要把/a 的路由解析到 pages/b.js,把/b 的路由解析到 pages/a.js,此时,就需要通过自定义,手动控制页面渲染来实现,在项目根目录下创建 server.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// server.js

const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl

if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
})
.listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})

你可以选择自己喜欢的服务端框架,express 或者 koa 等,进行自定义。

自定义

Next 提供了组件,可以自定义页面标签中的内容。每个组件都可以在内部自定义的内容:

1
2
3
4
5
6
7
8
9
10
import Head from 'next/head'
export default () => (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<p>Hello world!</p>
</div>
)

每个页面组件只需要定义本页面需要的内容,并且对于相同的标签,例如。会按照组件渲染的顺序,后定义的覆盖先定义的内容。</p> <h3 id="自定义-1"><a href="#自定义-1" class="headerlink" title="自定义"></a>自定义<Document></h3><p>在前面的例子中,服务端渲染时,所有的页面我们只需要写内容组件,这是因为使用了默认的<Document>模板。当然,可以自定义自己的服务端渲染模板。首先,创建 pages/_document.js 文件,写上内容:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">// pages/_document.js</span><br><span class="line">import Document, { Head, Main, NextScript } from 'next/document'</span><br><span class="line"></span><br><span class="line">export default class MyDocument extends Document {</span><br><span class="line"> static async getInitialProps (ctx) {</span><br><span class="line"> const props = await Document.getInitialProps(ctx)</span><br><span class="line"> return { ...props, customValue: 'hi there!' }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render () {</span><br><span class="line"> return (</span><br><span class="line"> <html></span><br><span class="line"> <Head></span><br><span class="line"> <style>{`body { margin: 0 } /* custom! */`}</style></span><br><span class="line"> </Head></span><br><span class="line"> <body className="custom_class"></span><br><span class="line"> {this.props.customValue}</span><br><span class="line"> <Main /></span><br><span class="line"> <NextScript /></span><br><span class="line"> </body></span><br><span class="line"> </html></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>其中的 ctx 对象与其他组件中的 getInitialProps 方法中收到的参数一样,只不过多了一个额外的方法:renderPage()。</p> <h3 id="自定义错误处理"><a href="#自定义错误处理" class="headerlink" title="自定义错误处理"></a>自定义错误处理</h3><p>Next 中,有一个默认组件 error.js,负责处理 404 或者 500 这种错误。当然,你也可以自定义一个_error.js 组件覆盖默认的错误处理组件:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">// _error.js</span><br><span class="line"></span><br><span class="line">import React from 'react'</span><br><span class="line">export default class Error extends React.Component {</span><br><span class="line"> static getInitialProps ({ res, xhr }) {</span><br><span class="line"> const statusCode = res ? res.statusCode : (xhr ? xhr.status : null)</span><br><span class="line"> return { statusCode }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render () {</span><br><span class="line"> return (</span><br><span class="line"> <p>{</span><br><span class="line"> this.props.statusCode</span><br><span class="line"> ? `An error ${this.props.statusCode} occurred on server`</span><br><span class="line"> : 'An error occurred on client'</span><br><span class="line"> }</p></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h3 id="自定义配置-1"><a href="#自定义配置-1" class="headerlink" title="自定义配置"></a>自定义配置</h3><p>相对 Next 进行自定义配置的话,可以在项目根目录下创建一个 next.config.js</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// next.config.js</span><br><span class="line"></span><br><span class="line">module.exports = {</span><br><span class="line"> /* 自定义配置 */</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h3 id="自定义-Webpack-配置"><a href="#自定义-Webpack-配置" class="headerlink" title="自定义 Webpack 配置"></a>自定义 Webpack 配置</h3><p>在创建好的 next.config.js 文件中,可以扩展 Webpack 配置:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">module.exports = {</span><br><span class="line"> webpack: (config, { dev }) => {</span><br><span class="line"></span><br><span class="line"> // 修改config对象</span><br><span class="line"></span><br><span class="line"> return config</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>该函数接收默认的 Webpack config 对象作为参数,返回修改后的 config 对象。需要注意的是,next.config.js 文件会被直接执行,因为只能使用本机安装的 Node.js 所支持的 JS 语法。</p> <p><strong>警告:不建议</strong>在自定义 Webpack 配置中添加 loader 以支持新的文件类型!因为只有客户端渲染的代码会经过打包,而服务端执行的是源代码,并没有经过 Webpack 处理,因此新的 loader 对服务端渲染不起作用。所以最好是使用 Babel 插件来处理新的文件类型,因为无论是客户端还是服务端渲染的代码,都会经过 Babel 处理。</p> <h3 id="自定义-Babel-配置"><a href="#自定义-Babel-配置" class="headerlink" title="自定义 Babel 配置"></a>自定义 Babel 配置</h3><p>自定义 Babel 配置,只需要在项目根目录下创建.babelrc 文件,因为自定义配置会覆盖默认配置,而不是扩展默认配置。因此需要把 next preset 写到.babelrc 中。例如:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "presets": [</span><br><span class="line"> "next/babel", // Next默认配置</span><br><span class="line"> "stage-0"</span><br><span class="line"> ],</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h3 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h3><p>生产模式下,需要先使用生产模式构建代码,再启动服务器。因此,需要两条命令:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">next build</span><br><span class="line">next start</span><br></pre></td></tr></table></figure> <p>Next 官方推荐使用<a target="_blank" rel="noopener" href="https://zeit.co/now">now</a>作为部署工具,只要在 package.json 文件中写入:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "my-app",</span><br><span class="line"> "dependencies": {</span><br><span class="line"> "next": "latest"</span><br><span class="line"> },</span><br><span class="line"> "scripts": {</span><br><span class="line"> "dev": "next",</span><br><span class="line"> "build": "next build",</span><br><span class="line"> "start": "next start"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>接着运行 now 命令,就可以实现一键部署。</p> </div> <footer class="post-footer"> <div class="post-eof"></div> </footer> </article> <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN"> <link itemprop="mainEntityOfPage" href="http://example.com/2022/12/16/React-%E5%BC%80%E5%8F%91%E4%B9%8BUmi%E4%B9%8C%E7%B1%B3%E6%A1%86%E6%9E%B6%E4%BD%BF%E7%94%A8/"> <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person"> <meta itemprop="image" content="/images/portal.jpg"> <meta itemprop="name" content="Bruce Chen"> <meta itemprop="description" content="It's better to burn out than to fade away."> </span> <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"> <meta itemprop="name" content="Bruce Chen's Blog"> </span> <header class="post-header"> <h2 class="post-title" itemprop="name headline"> <a href="/2022/12/16/React-%E5%BC%80%E5%8F%91%E4%B9%8BUmi%E4%B9%8C%E7%B1%B3%E6%A1%86%E6%9E%B6%E4%BD%BF%E7%94%A8/" class="post-title-link" itemprop="url">React 开发之Umi乌米框架使用</a> </h2> <div class="post-meta"> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-calendar"></i> </span> <span class="post-meta-item-text">发表于</span> <time title="创建时间:2022-12-16 14:45:55 / 修改时间:14:51:01" itemprop="dateCreated datePublished" datetime="2022-12-16T14:45:55+08:00">2022-12-16</time> </span> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-folder"></i> </span> <span class="post-meta-item-text">分类于</span> <span itemprop="about" itemscope itemtype="http://schema.org/Thing"> <a href="/categories/%E5%89%8D%E7%AB%AFUI%E6%A1%86%E6%9E%B6/" itemprop="url" rel="index"><span itemprop="name">前端UI框架</span></a> </span> </span> </div> </header> <div class="post-body" itemprop="articleBody"> <p>以往搭建 React 应用时往往使用官方推荐的 # create-react-app 不过使用官方的脚手架往往不能很好的适应我们现有的项目,比如我们要集合 webpack 打包?我们要引入 Redux 状态管理器?Umi (乌米)框架应运而生。</p> <p>让我们来看看官方的自我介绍:</p> <blockquote> <p>umi 以路由为基础的,支持类 next.js 的约定式路由,以及各种进阶的路由功能,并以此进行功能扩展,比如支持路由级的按需加载。然后配以完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求,目前内外部加起来已有 50+ 的插件。</p> </blockquote> <p>再让我们看看 Umi 的特色:</p> <ul> <li>📦 开箱即用,内置 react、react-router 等</li> <li>🏈 类 next.js 且功能完备的路由约定,同时支持配置的路由方式</li> <li>🎉 完善的插件体系,覆盖从源码到构建产物的每个生命周期</li> <li>🚀 高性能,通过插件支持 PWA、以路由为单元的 code splitting 等</li> <li>💈 支持静态页面导出,适配各种环境,比如中台业务、无线业务、egg、支付宝钱包、云凤蝶等</li> <li>🚄 开发启动快,支持一键开启 dll 等</li> <li>🐠 一键兼容到 IE9,基于 umi-plugin-polyfills</li> <li>🍁 完善的 TypeScript 支持,包括 d.ts 定义和 umi test</li> <li>🌴 与 dva 数据流的深入融合,支持 duck directory、model 的自动加载、code splitting 等等</li> </ul> <h2 id="一、搭建流程"><a href="#一、搭建流程" class="headerlink" title="一、搭建流程"></a>一、搭建流程</h2><p>现在我们自己手动搭建一套完整可用的 Umi 框架吧</p> <h3 id="第一步:环境检测及安装"><a href="#第一步:环境检测及安装" class="headerlink" title="第一步:环境检测及安装"></a>第一步:环境检测及安装</h3><p>首先需要 node, 并确保 node 版本是 8.10 或以上。(mac 下推荐使用 nvm 来管理 node 版本)<br>而包管理器,这里推荐使用 yarn 管理 npm 依赖</p> <p>1.安装 yarn<br>可根据官网介绍选择安装方式 <a target="_blank" rel="noopener" href="https://yarnpkg.com/zh-Hans/docs/install#windows-stable">https://yarnpkg.com/zh-Hans/docs/install#windows-stable</a></p> <p>2.全局安装 umi,并确保版本是 2.0.0 或以上。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn global add umi</span><br></pre></td></tr></table></figure> <h3 id="第二步:通过脚手架创建项目"><a href="#第二步:通过脚手架创建项目" class="headerlink" title="第二步:通过脚手架创建项目"></a>第二步:通过脚手架创建项目</h3><p>umi 通过 create-umi 提供脚手架能力</p> <ol> <li>在需要生成项目的文件夹下,打开 CMD or 终端 命令行输入 yarn create umi</li> </ol> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn create umi</span><br></pre></td></tr></table></figure> <ol start="2"> <li>选择需要生成的项目类型</li> </ol> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">? Select the boilerplate type (Use arrow keys)</span><br><span class="line"> ant-design-pro - Create project with an layout-only ant-design-pro boilerplate, use together with umi block.</span><br><span class="line">❯ app - Create project with a simple boilerplate, support typescript.</span><br><span class="line"> block - Create a umi block.</span><br><span class="line"> library - Create a library with umi.</span><br><span class="line"> plugin - Create a umi plugin.</span><br></pre></td></tr></table></figure> <ul> <li>app,通用项目脚手架,支持选择是否启用 TypeScript,以及 umi-plugin-react 包含的功能</li> <li>ant-design-pro,仅包含 ant-design-pro 布局的脚手架,具体页面可通过 umi block 添加</li> <li>block,区块脚手架</li> <li>plugin,插件脚手架</li> <li>library,依赖(组件)库脚手架,基于 umi-plugin-library<br>在此我们 上下箭头切换到 app 并回车确定选择。</li> </ul> <ol start="3"> <li>其他选项</li> </ol> <p>此时会出现提示是否需要支持 Typescript, 可根据实际项目开发情况选择是否使用。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">? Do you want to use typescript? (y/N)</span><br></pre></td></tr></table></figure> <p>选择 Typescript 支持后再选择你需要的功能(多选),功能介绍详见 <a target="_blank" rel="noopener" href="https://umijs.org/zh/plugin/umi-plugin-react.html">plugin/umi-plugin-react</a>。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">? What functionality do you want to enable? (Press <space> to select, <a> to toggle all, <i> to invert selection)</span><br><span class="line">❯◯ antd</span><br><span class="line"> ◯ dva</span><br><span class="line"> ◯ code splitting</span><br><span class="line"> ◯ dll</span><br></pre></td></tr></table></figure> <p>同样按上下箭头移动,并按 空格 键选中需要的功能。<br>antd: UI 框架,启用后实现 antd, antd-mobile 和 antd-pro 的按需编译,无需要手动配置。<br>dva: 基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架<br>code splitting: 是否代码分包<br>dll: 通过 webpack 的 dll 插件预打包一份 dll 文件来达到二次启动提速的目的<br>注意 此处多选项未选择,后期也可以在配置文件中配置。</p> <p>确定后,会根据你的选择自动创建好目录和文件。</p> <h3 id="第三步:运行及编译"><a href="#第三步:运行及编译" class="headerlink" title="第三步:运行及编译"></a>第三步:运行及编译</h3><p>安装依赖</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn</span><br></pre></td></tr></table></figure> <p>启动项目</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn start</span><br></pre></td></tr></table></figure> <p>这样我们就可以愉快的开发和调试了。</p> <p>当我们开发好要编译项目发布测试 or 生产时,执行:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn build</span><br></pre></td></tr></table></figure> <h2 id="二、目录结构说明"><a href="#二、目录结构说明" class="headerlink" title="二、目录结构说明"></a>二、目录结构说明</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── dist/ // 默认的 build 输出目录</span><br><span class="line">├── mock/ // mock 文件所在目录,基于 express</span><br><span class="line">├── config/</span><br><span class="line">├── config.js // umi 配置,同 .umirc.js,二选一, 建议配置.umirc.js</span><br><span class="line">└── src/ // 源码目录,可选</span><br><span class="line">├── layouts/index.js // 全局布局</span><br><span class="line">├── pages/ // 页面目录,里面的文件即路由</span><br><span class="line">├── .umi/ // dev 临时目录,需添加到 .gitignore</span><br><span class="line">├── .umi-production/ // build 临时目录,会自动删除</span><br><span class="line">├── document.ejs // HTML 模板</span><br><span class="line">├── 404.js // 404 页面</span><br><span class="line">├── page1.js // 页面 1,任意命名,导出 react 组件</span><br><span class="line">├── page1.test.js // 用例文件,umi test 会匹配所有 .test.js 和 .e2e.js 结尾的文件</span><br><span class="line">└── page2.js // 页面 2,任意命名</span><br><span class="line">├── global.css // 约定的全局样式文件,自动引入,也可以用 global.less</span><br><span class="line">├── global.js // 可以在这里加入 polyfill</span><br><span class="line">├── app.js // 运行时配置文件</span><br><span class="line">├── .umirc.js // umi 配置,同 config/config.js,二选一</span><br><span class="line">├── .env // 环境变量</span><br><span class="line">└── package.json</span><br></pre></td></tr></table></figure> <h2 id="三、开发提示"><a href="#三、开发提示" class="headerlink" title="三、开发提示"></a>三、开发提示</h2><p>.umirc.js 配置,<br>该文件中可以配置项目基本情况,如上面安装步骤中出现是否选用 antd, dva 等,在此文件中都可以更改为 true, 另外还可以配置 webpack 打包配置,具体的配置项详见.umirc.js 配置。</p> <p>设置.umirc.local.js 文件<br>由于.umirc 里的配置众多而且为了优化项目必然会分包代码抽离等,而在本地运行时无需这些,便可配置.umirc.local.js 文件,注意此文件中的选项配置和.umirc.js 配置一样,不要提交到 git,所以通常需要配置到 .gitignore。本地运行时会和 .umirc.js 合并后再返回。</p> <p>由于 umi 会根据 pages 目录自动生成路由配置,所以无需要手动配置路由,会根据 src / pages 下 文件名自动生成路由,但是你也可以配置.umirc.js 中的 routes 属性,此配置项存在时则不会对 src/pages 目录做约定式的解析。</p> <p>常用路由操作</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">import React, { PureComponent } from 'react';</span><br><span class="line">import Link from 'umi/link';</span><br><span class="line">import router from 'umi/router';</span><br><span class="line"></span><br><span class="line">class Examples extends PureComponent {</span><br><span class="line"> render () {</span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> {/* 普通使用 */}</span><br><span class="line"> <Link to="/list">跳转</Link></span><br><span class="line"></span><br><span class="line"> {/* 带参数 */}</span><br><span class="line"> <Link to="/list?a=b">跳转</Link></span><br><span class="line"></span><br><span class="line"> {/* 包含子组件 */}</span><br><span class="line"> <Link to="/list?a=b"><button>跳转</button></Link></span><br><span class="line"></span><br><span class="line"> {/* 点击跳转 */}</span><br><span class="line"> <button onClick={() => router.push('/list')}>跳转</button></span><br><span class="line"> </></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">export default Examples;</span><br></pre></td></tr></table></figure> <p>更多查看<a target="_blank" rel="noopener" href="https://umijs.org/zh/api/#%E8%B7%AF%E7%94%B1">路由配置</a></p> <h2 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h2><p>umi 框架为我们开发项目提升了效率,而其本身也在不断的升级完善中,在 2.8.0+版本,umi 可配置 ssr 服务器端渲染,相信 umi 还将越来越完善。<br>更多查看官方文档 <a target="_blank" rel="noopener" href="https://umijs.org/">https://umijs.org/</a></p> </div> <footer class="post-footer"> <div class="post-eof"></div> </footer> </article> <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN"> <link itemprop="mainEntityOfPage" href="http://example.com/2022/12/16/antd-%E5%B8%B8%E7%94%A8%E7%9F%A5%E8%AF%86%E7%82%B9%E5%92%8C%E5%B0%8F%E6%8A%80%E5%B7%A7%E6%80%BB%E7%BB%93/"> <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person"> <meta itemprop="image" content="/images/portal.jpg"> <meta itemprop="name" content="Bruce Chen"> <meta itemprop="description" content="It's better to burn out than to fade away."> </span> <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"> <meta itemprop="name" content="Bruce Chen's Blog"> </span> <header class="post-header"> <h2 class="post-title" itemprop="name headline"> <a href="/2022/12/16/antd-%E5%B8%B8%E7%94%A8%E7%9F%A5%E8%AF%86%E7%82%B9%E5%92%8C%E5%B0%8F%E6%8A%80%E5%B7%A7%E6%80%BB%E7%BB%93/" class="post-title-link" itemprop="url">antd 常用知识点和小技巧总结</a> </h2> <div class="post-meta"> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-calendar"></i> </span> <span class="post-meta-item-text">发表于</span> <time title="创建时间:2022-12-16 14:45:48 / 修改时间:14:48:44" itemprop="dateCreated datePublished" datetime="2022-12-16T14:45:48+08:00">2022-12-16</time> </span> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-folder"></i> </span> <span class="post-meta-item-text">分类于</span> <span itemprop="about" itemscope itemtype="http://schema.org/Thing"> <a href="/categories/%E5%89%8D%E7%AB%AFUI%E6%A1%86%E6%9E%B6/" itemprop="url" rel="index"><span itemprop="name">前端UI框架</span></a> </span> </span> </div> </header> <div class="post-body" itemprop="articleBody"> <h2 id="1-form-表单中-FormItem-的布局"><a href="#1-form-表单中-FormItem-的布局" class="headerlink" title="1 form 表单中 FormItem 的布局"></a>1 form 表单中 FormItem 的布局</h2><p>使用 getFieldDecorator 包裹的输入框或者 Select,必须是在最外层,也就是只有一层,否则,检验会一直不通过,所以,需要重新布局应该在 getFieldDecorator 的外层添加父节点,而不应该在里面。</p> <p><strong>例:</strong></p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><FormItem</span><br><span class="line"> {...formItemLayout}</span><br><span class="line"> label="所属应用"</span><br><span class="line">></span><br><span class="line"> <div></span><br><span class="line"> {getFieldDecorator('apiwgAppName', {</span><br><span class="line"> rules: [{ required: false, message: '请选择' }],</span><br><span class="line"> initialValue: apiwgAppName || ""</span><br><span class="line"> })(</span><br><span class="line"> <Input disabled={this.store.data.apiId ? true : false}</span><br><span class="line"> className="control-special" readOnly style={{ width: "70%" }}</span><br><span class="line"> onClick={this.showModal.bind(this, "apiwgApp")} /></span><br><span class="line"> )}</span><br><span class="line"> <Button className="btn-modal" type="primary" onClick={this.showModal.bind(this, "apiwgApp")}</span><br><span class="line"> disabled={this.store.data.apiId ? true : false}</span><br><span class="line"> >选择所属应用</span><br><span class="line"> </Button></span><br><span class="line"> <a</span><br><span class="line"> style={{ marginLeft: '8px' }}</span><br><span class="line"> onClick={this.openNewAppDlg.bind(this)}</span><br><span class="line"> className={`api-add ${</span><br><span class="line"> this.store.data.apiId ? 'disabled' : ''</span><br><span class="line"> }`}</span><br><span class="line"> ></span><br><span class="line"> +新增应用</span><br><span class="line"> </a></span><br><span class="line"> </div></span><br><span class="line"></FormItem></span><br></pre></td></tr></table></figure> <h2 id="2-form-表单,FormItem-的-rules-中新增-validator,实时请求校验"><a href="#2-form-表单,FormItem-的-rules-中新增-validator,实时请求校验" class="headerlink" title="2 form 表单,FormItem 的 rules 中新增 validator,实时请求校验"></a>2 form 表单,FormItem 的 rules 中新增 validator,实时请求校验</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><FormItem</span><br><span class="line"> labelCol={{ span: 8 }}</span><br><span class="line"> wrapperCol={{ span: 15 }}</span><br><span class="line"> label="菜单名称"</span><br><span class="line">></span><br><span class="line"> {form.getFieldDecorator('menuName', {</span><br><span class="line"> rules: [</span><br><span class="line"> { required: true, message: '菜单名称不能为空' },</span><br><span class="line"> { type: 'string', max: 30, message: '菜单名称过长' },</span><br><span class="line"> { validator: this.handleCheckName },</span><br><span class="line"> { whitespace: true, message: '请输入非空白内容' }</span><br><span class="line"> ],</span><br><span class="line"> initialValue: this.props.menuSysData.menuName,</span><br><span class="line"> })(</span><br><span class="line"> <Input</span><br><span class="line"> // placeholder="请输入菜单名称"</span><br><span class="line"> disabled={disableFlag}</span><br><span class="line"> /></span><br><span class="line"> )}</span><br><span class="line"></FormItem></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">// 实时校验</span><br><span class="line"> handleCheckName = (rule, value, callback) => {</span><br><span class="line"> const { checkName, actionType } = this.state;</span><br><span class="line"> if (!this.trim(value) || (checkName && actionType === 'M' && this.trim(value) === checkName)) {</span><br><span class="line"> callback();</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> let params = {</span><br><span class="line"> menuName: value,</span><br><span class="line"> state: "00A"</span><br><span class="line"> };</span><br><span class="line"> MenuSysService.checkMenuName(params).then(result => {</span><br><span class="line"> if (!result || !result.resultObject) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> let code = result.resultObject.code;</span><br><span class="line"> if (code && code > 0) {</span><br><span class="line"> callback('系统名称已存在!');</span><br><span class="line"> }</span><br><span class="line"> callback();</span><br><span class="line"> });</span><br><span class="line"> }</span><br></pre></td></tr></table></figure> <h2 id="3-利用-validator-和正则,验证中文"><a href="#3-利用-validator-和正则,验证中文" class="headerlink" title="3 利用 validator 和正则,验证中文"></a>3 利用 validator 和正则,验证中文</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><FormItem</span><br><span class="line"> hasFeedback={!disableFlag}</span><br><span class="line"> labelCol={{ span: 6 }}</span><br><span class="line"> wrapperCol={{ span: 15 }}</span><br><span class="line"> label="账号" ></span><br><span class="line"> {form.getFieldDecorator('userCode', {</span><br><span class="line"> initialValue: '',</span><br><span class="line"> rules: [</span><br><span class="line"> { required: !disableFlag, validator: this.usercodeValidator },</span><br><span class="line"> { type: 'string', max: 30, message: '账号过长' },</span><br><span class="line"> { whitespace: true, message: '内容不能为空' }</span><br><span class="line"> ],</span><br><span class="line"> })(</span><br><span class="line"> <Input placeholder="请输入账号" disabled={account} maxLength="30" autoComplete="false" />)}</span><br><span class="line"></FormItem></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">usercodeValidator = (rule, value, callback) => {</span><br><span class="line"> const { userData } = this.props;</span><br><span class="line"> if (!value) {</span><br><span class="line"> callback('内容不能为空');</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // !!!中文验证</span><br><span class="line"> const reg = /[\u4E00-\u9FA5]{1,4}/; /*定义验证表达式*/</span><br><span class="line"> if (reg.test(value)) { /*进行验证*/</span><br><span class="line"> callback('账号不能为中文');</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> if (userData.userCode === value) {</span><br><span class="line"> callback();</span><br><span class="line"> }</span><br><span class="line"> else {</span><br><span class="line"> let params = {</span><br><span class="line"> userCode: value + "", // 查一下有没有这个编码</span><br><span class="line"> useState: '10301'</span><br><span class="line"> };</span><br><span class="line"> SysUserMgService.checkUserCode(params).then(result => {</span><br><span class="line"> if (!result || result.code !== '0') {</span><br><span class="line"> callback(result.message);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> if (result.resultObject && result.resultObject.num !== 0) {</span><br><span class="line"> callback('该账号已存在');</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> callback();</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure> <h2 id="4-form-validateFields-直接获取表单的值"><a href="#4-form-validateFields-直接获取表单的值" class="headerlink" title="4 form.validateFields 直接获取表单的值"></a>4 form.validateFields 直接获取表单的值</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">this.props.form.validateFields((err, fieldsValue) => {</span><br><span class="line"> if (err) return;</span><br><span class="line"> this.handleSubmit(fieldsValue);</span><br><span class="line"> });</span><br></pre></td></tr></table></figure> <h2 id="5-form-表单提交-htmlType,改为-onClick"><a href="#5-form-表单提交-htmlType,改为-onClick" class="headerlink" title="5 form 表单提交 htmlType,改为 onClick"></a>5 form 表单提交 htmlType,改为 onClick</h2><p>说明:因为之前遇到过使用 htmlType 提交表单会有问题,但是改为 onClick 后,就没问题了,所以,也记录一下。<br>htmlType 是官网使用的方式,具体问题本人当时忘记截个图了。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><Form layout="inline" onSubmit={this.handleSubmit}></span><br><span class="line"> <FormItem</span><br><span class="line"> validateStatus={userNameError ? 'error' : ''}</span><br><span class="line"> help={userNameError || ''}</span><br><span class="line"> ></span><br><span class="line"> {getFieldDecorator('userName', {</span><br><span class="line"> rules: [{ required: true, message: 'Please input your username!' }],</span><br><span class="line"> })(</span><br><span class="line"> <Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" /></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br><span class="line"> <FormItem</span><br><span class="line"> validateStatus={passwordError ? 'error' : ''}</span><br><span class="line"> help={passwordError || ''}</span><br><span class="line"> ></span><br><span class="line"> {getFieldDecorator('password', {</span><br><span class="line"> rules: [{ required: true, message: 'Please input your Password!' }],</span><br><span class="line"> })(</span><br><span class="line"> <Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="Password" /></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br><span class="line"> <FormItem></span><br><span class="line"> <Button</span><br><span class="line"> type="primary"</span><br><span class="line"> htmlType="submit"</span><br><span class="line"> disabled={hasErrors(getFieldsError())}</span><br><span class="line"> ></span><br><span class="line"> Log in</span><br><span class="line"> </Button></span><br><span class="line"> </FormItem></span><br><span class="line"> </Form></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">// 改变后:</span><br><span class="line"><Form layout="inline" ></span><br><span class="line"> <FormItem</span><br><span class="line"> validateStatus={userNameError ? 'error' : ''}</span><br><span class="line"> help={userNameError || ''}</span><br><span class="line"> ></span><br><span class="line"> {getFieldDecorator('userName', {</span><br><span class="line"> rules: [{ required: true, message: 'Please input your username!' }],</span><br><span class="line"> })(</span><br><span class="line"> <Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" /></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br><span class="line"> <FormItem</span><br><span class="line"> validateStatus={passwordError ? 'error' : ''}</span><br><span class="line"> help={passwordError || ''}</span><br><span class="line"> ></span><br><span class="line"> {getFieldDecorator('password', {</span><br><span class="line"> rules: [{ required: true, message: 'Please input your Password!' }],</span><br><span class="line"> })(</span><br><span class="line"> <Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="Password" /></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br><span class="line"> <FormItem></span><br><span class="line"> <Button</span><br><span class="line"> type="primary"</span><br><span class="line"> disabled={hasErrors(getFieldsError())}</span><br><span class="line"> onClick={() => this.handleSubmit()}</span><br><span class="line"> ></span><br><span class="line"> Log in</span><br><span class="line"> </Button></span><br><span class="line"> </FormItem></span><br><span class="line"> </Form></span><br></pre></td></tr></table></figure> <h2 id="6-Input-组件,利用-maxLength-属性,限制最大输入内容长度"><a href="#6-Input-组件,利用-maxLength-属性,限制最大输入内容长度" class="headerlink" title="6 Input 组件,利用 maxLength 属性,限制最大输入内容长度"></a>6 Input 组件,利用 maxLength 属性,限制最大输入内容长度</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><Input</span><br><span class="line"> placeholder="请输入账号"</span><br><span class="line"> disabled={account}</span><br><span class="line"> maxLength="30"</span><br><span class="line"> autoComplete="off"</span><br><span class="line">/></span><br></pre></td></tr></table></figure> <h2 id="7-InputNumber-只能输入数字:"><a href="#7-InputNumber-只能输入数字:" class="headerlink" title="7 InputNumber 只能输入数字:"></a>7 InputNumber 只能输入数字:</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><InputNumber</span><br><span class="line"> formatter={value => value}</span><br><span class="line"> parser={value => parseInt(value) || ''}</span><br><span class="line"> style={{ width: '100%' }}</span><br><span class="line"> step={1}</span><br><span class="line"> onChange={(val) => this.onChangeIpt(1, val)}</span><br><span class="line"> /></span><br></pre></td></tr></table></figure> <h2 id="8-menu-实现回缩效果注意点"><a href="#8-menu-实现回缩效果注意点" class="headerlink" title="8 menu 实现回缩效果注意点"></a>8 menu 实现回缩效果注意点</h2><p>说明:menu 必须放在 Sider 中,才能实现缩回去的,这个有特定的布局。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><Sider</span><br><span class="line"> style={{ background: '#1D2023', height: '100%' }}</span><br><span class="line"> trigger={null}</span><br><span class="line"> collapsible</span><br><span class="line"> collapsed={this.state.collapsed}</span><br><span class="line"> width={140}</span><br><span class="line"> collapsedWidth={40}</span><br><span class="line">></span><br><span class="line"> <BaseMenu</span><br><span class="line"> toggle={this.toggle}</span><br><span class="line"> collapsed={this.state.collapsed}</span><br><span class="line"> history={history}</span><br><span class="line"> location={location}</span><br><span class="line"> /></span><br><span class="line"></Sider></span><br></pre></td></tr></table></figure> <h2 id="9-左侧菜单调整宽度设置"><a href="#9-左侧菜单调整宽度设置" class="headerlink" title="9 左侧菜单调整宽度设置"></a>9 左侧菜单调整宽度设置</h2><p>说明:通过在 Sider 组件,设置 width,调整菜单的宽度,通过设置 collapsedWidth,调整缩进的宽度。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><Sider</span><br><span class="line"> style={{ background: '#1D2023', height: '100%' }}</span><br><span class="line"> trigger={null}</span><br><span class="line"> collapsible</span><br><span class="line"> collapsed={this.state.collapsed}</span><br><span class="line"> width={140}</span><br><span class="line"> collapsedWidth={40}</span><br><span class="line">></span><br><span class="line"> <BaseMenu</span><br><span class="line"> toggle={this.toggle}</span><br><span class="line"> collapsed={this.state.collapsed}</span><br><span class="line"> history={history}</span><br><span class="line"> location={location}</span><br><span class="line"> /></span><br><span class="line"></Sider></span><br></pre></td></tr></table></figure> <h2 id="10-表格-Columns-字段-id-页面不展示情况"><a href="#10-表格-Columns-字段-id-页面不展示情况" class="headerlink" title="10 表格 Columns 字段 id 页面不展示情况"></a>10 表格 Columns 字段 id 页面不展示情况</h2><p>说明:一般而言,表格 Columns 字段 id 是在界面不展示的,但是,对于有些逻辑的处理,又是需要的,可以使用相应样式隐藏的处理方式。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">常规展示的情况:</span><br><span class="line">{</span><br><span class="line"> title: '序号',</span><br><span class="line"> dataIndex: 'algoId',</span><br><span class="line"> key: 'algoId'</span><br><span class="line">},</span><br><span class="line"></span><br><span class="line">不展示id字段:</span><br><span class="line">{</span><br><span class="line"> title: '',</span><br><span class="line"> dataIndex: 'algoId',</span><br><span class="line"> key: 'algoId',</span><br><span class="line"> width: 0,</span><br><span class="line"> render: item => {</span><br><span class="line"> return (</span><br><span class="line"> <span style={{ display: 'none' }} title={item}></span><br><span class="line"> {item}</span><br><span class="line"> </span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">},</span><br></pre></td></tr></table></figure> <h2 id="11-自定义-Modal"><a href="#11-自定义-Modal" class="headerlink" title="11 自定义 Modal"></a>11 自定义 Modal</h2><p>查看元素可知,Modal 是在界面构建完成之后,由 js 控制,动态的添加,所以想事先获取 ant-modal-body 中 DOM 元素的节点是不可能的,但是一般情况也不会去获取它。<br>自定义 Modal,解决上述的问题。</p> <p><strong>关键代码</strong>:</p> <p>说明:</p> <p>1:因为我们使用的是 antd,所以,下面的样式是不需要引入的。这个跟 antd 的 Modal 样式重复。</p> <p>2:Modal 的隐藏和显示,是通过控制 class 为 ant-modal-mask 和 ant-modal-wrap 两个 div 的显示和隐藏。</p> <ul> <li>通过给 ant-modal-mask 的 div,添加另外一个 className:ant-modal-mask-hidden,来控制其隐藏,也可以通过 display 来控制。</li> <li>通过给 ant-modal-wrap 设置行内样式 display: none,来控制其隐藏。不过,也可以使用 className,随便都可以。</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">界面布局:</span><br><span class="line"></span><br><span class="line"><div className="ant-modal-mask" ></div></span><br><span class="line"><div tabIndex="-1" className="ant-modal-wrap " role="dialog" aria-labelledby="rcDialogTitle0" style={{}}></span><br><span class="line"> <div role="document" className="ant-modal" style={{ width: '920px' }}></span><br><span class="line"> <div className="ant-modal-content"></span><br><span class="line"> <div className="ant-modal-header"></span><br><span class="line"> ...</span><br><span class="line"> </div></span><br><span class="line"> <div className="ant-modal-body" style={{ background: 'rgb(16, 16, 17)' }}></span><br><span class="line"> ...</span><br><span class="line"> </div></span><br><span class="line"> </div></span><br><span class="line"> </div></span><br><span class="line"> <div tabIndex="0" style={{ width: '0px', height: '0px', overflow: 'hidden' }}></span><br><span class="line"> sentinel</span><br><span class="line"> </div></span><br><span class="line"></div></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">样式:</span><br><span class="line">.ant-modal-mask { // 遮罩层</span><br><span class="line"> position: fixed;</span><br><span class="line"> top: 0;</span><br><span class="line"> right: 0;</span><br><span class="line"> left: 0;</span><br><span class="line"> bottom: 0;</span><br><span class="line"> background-color: rgba(0, 0, 0, 0.65);</span><br><span class="line"> height: 100%;</span><br><span class="line"> z-index: 1000;</span><br><span class="line"> filter: alpha(opacity=50);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">.ant-modal-wrap {</span><br><span class="line"> position: fixed;</span><br><span class="line"> overflow: auto;</span><br><span class="line"> top: 0;</span><br><span class="line"> right: 0;</span><br><span class="line"> bottom: 0;</span><br><span class="line"> left: 0;</span><br><span class="line"> z-index: 1000;</span><br><span class="line"> -webkit-overflow-scrolling: touch;</span><br><span class="line"> outline: 0;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">.ant-modal {</span><br><span class="line"> font-family: "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";</span><br><span class="line"> font-size: 14px;</span><br><span class="line"> font-variant: tabular-nums;</span><br><span class="line"> line-height: 1.5;</span><br><span class="line"> color: rgba(0, 0, 0, 0.65);</span><br><span class="line"> -webkit-box-sizing: border-box;</span><br><span class="line"> box-sizing: border-box;</span><br><span class="line"> margin: 0;</span><br><span class="line"> padding: 0;</span><br><span class="line"> list-style: none;</span><br><span class="line"> position: relative;</span><br><span class="line"> width: 920px;</span><br><span class="line"> margin: 0 auto;</span><br><span class="line"> top: 100px;</span><br><span class="line"> padding-bottom: 24px;</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h2 id="12-Select-组件清除选框内容"><a href="#12-Select-组件清除选框内容" class="headerlink" title="12 Select 组件清除选框内容"></a>12 Select 组件清除选框内容</h2><p>通过给 Select 组件新增 allowClear 属性。注意:allowClear 也会触发 onChange 方法,所以,也要单独处理一下,因为 value 和 element 为 undefined。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><Select {...this.props} placeholder="请选择" allowClear={true} ></span><br><span class="line"> ...</span><br><span class="line"></Select></span><br></pre></td></tr></table></figure> <h2 id="13-antd、mobx-注入的顺序"><a href="#13-antd、mobx-注入的顺序" class="headerlink" title="13 antd、mobx @注入的顺序"></a>13 antd、mobx @注入的顺序</h2><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82NjMwMjY1LTNlNmVjMDRjNDg1YTA1MWMucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXB8aW1hZ2VWaWV3Mi8yL3cvMTAyNS9mb3JtYXQvd2VicA?x-oss-process=image/format,png" alt="image"></p> <p>顺序</p> <h2 id="14-解决-table-组件,-key-警告"><a href="#14-解决-table-组件,-key-警告" class="headerlink" title="14 解决 table 组件, key 警告"></a>14 解决 table 组件, key 警告</h2><p>一般都是使用 rowkey 方法一解决(后台数据要保证没有重复);</p> <h3 id="14-1-方法一:一般都是使用-rowkey-解决(后台数据要保证没有重复);"><a href="#14-1-方法一:一般都是使用-rowkey-解决(后台数据要保证没有重复);" class="headerlink" title="14.1 方法一:一般都是使用 rowkey 解决(后台数据要保证没有重复);"></a>14.1 方法一:一般都是使用 rowkey 解决(后台数据要保证没有重复);</h3><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82NjMwMjY1LTUyYzkwZGFlODk1ZmQ1NzYucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXB8aW1hZ2VWaWV3Mi8yL3cvNTczL2Zvcm1hdC93ZWJw?x-oss-process=image/format,png" alt="image"></p> <p>使用 rowKey</p> <h3 id="14-2-方法二:dataSource-数据新增-key"><a href="#14-2-方法二:dataSource-数据新增-key" class="headerlink" title="14.2 方法二:dataSource 数据新增 key"></a>14.2 方法二:dataSource 数据新增 key</h3><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82NjMwMjY1LWVlNmQ2YjBlMDU1NTIyNTgucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXB8aW1hZ2VWaWV3Mi8yL3cvNTg4L2Zvcm1hdC93ZWJw?x-oss-process=image/format,png" alt="image"></p> <p>dataSource 数据新增 key</p> <h2 id="15-Form-create-方式"><a href="#15-Form-create-方式" class="headerlink" title="15 Form.create 方式"></a>15 Form.create 方式</h2><h3 id="15-1-方式一:-注解"><a href="#15-1-方式一:-注解" class="headerlink" title="15.1 方式一:@注解"></a>15.1 方式一:@注解</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">@Form.create({})</span><br></pre></td></tr></table></figure> <h3 id="15-2-方式二:高阶写法"><a href="#15-2-方式二:高阶写法" class="headerlink" title="15.2 方式二:高阶写法"></a>15.2 方式二:高阶写法</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export default (Form.create({})(APP));</span><br></pre></td></tr></table></figure> <h2 id="16-Form-initialValue-值编辑后,表单的值不改变问题"><a href="#16-Form-initialValue-值编辑后,表单的值不改变问题" class="headerlink" title="16 Form initialValue 值编辑后,表单的值不改变问题"></a>16 Form initialValue 值编辑后,表单的值不改变问题</h2><h3 id="16-1-方法一"><a href="#16-1-方法一" class="headerlink" title="16.1 方法一"></a>16.1 方法一</h3><p>其实,只要编辑成功后,回调调用 form.resetFields(),就可以了,如果<br>是使用 modal 框弹出的表单,就可以直接使用 destroyOnClose = {true} 属性。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br></pre></td><td class="code"><pre><span class="line">import React from 'react';</span><br><span class="line">import { Input, Modal, Form } from 'antd';</span><br><span class="line">import styles from './UserModal.less';</span><br><span class="line"></span><br><span class="line">const FormItem = Form.Item;</span><br><span class="line"></span><br><span class="line">const UserModal = ({ currentItem, dispatch, form, visible }) => {</span><br><span class="line"></span><br><span class="line"> function handleOk() {</span><br><span class="line"> form.validateFields((err, fieldsValue) => {</span><br><span class="line"> if (err) return;</span><br><span class="line"> dispatch({</span><br><span class="line"> type: 'demo/update',</span><br><span class="line"> payload: {</span><br><span class="line"> currentItem: fieldsValue</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> function handleCancel() {</span><br><span class="line"> dispatch({</span><br><span class="line"> type: 'demo/hideModal'</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> const formItemLayout = {</span><br><span class="line"> labelCol: {</span><br><span class="line"> xs: { span: 24 },</span><br><span class="line"> sm: { span: 4 },</span><br><span class="line"> },</span><br><span class="line"> wrapperCol: {</span><br><span class="line"> xs: { span: 24 },</span><br><span class="line"> sm: { span: 20 },</span><br><span class="line"> },</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> const { getFieldDecorator } = form;</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <div className={styles.root}></span><br><span class="line"> <Modal</span><br><span class="line"> title="编辑"</span><br><span class="line"> visible={visible}</span><br><span class="line"> onOk={() => handleOk()}</span><br><span class="line"> onCancel={() => handleCancel()}</span><br><span class="line"> destroyOnClose={true}</span><br><span class="line"> ></span><br><span class="line"> <Form></span><br><span class="line"> <FormItem</span><br><span class="line"> {...formItemLayout}</span><br><span class="line"> label="用户名"</span><br><span class="line"> ></span><br><span class="line"> {getFieldDecorator('name', {</span><br><span class="line"> initialValue: currentItem.name,</span><br><span class="line"> rules: [{</span><br><span class="line"> required: true, message: 'Please input your name!',</span><br><span class="line"> }],</span><br><span class="line"> })(</span><br><span class="line"> <Input placeholder="请输入用户名" /></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br><span class="line"> <FormItem</span><br><span class="line"> {...formItemLayout}</span><br><span class="line"> label="年龄"</span><br><span class="line"> ></span><br><span class="line"> {getFieldDecorator('age', {</span><br><span class="line"> initialValue: currentItem.age,</span><br><span class="line"> rules: [{</span><br><span class="line"> required: true, message: 'Please input your age!',</span><br><span class="line"> }],</span><br><span class="line"> })(</span><br><span class="line"> <Input placeholder="请输入年龄" /></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br><span class="line"> <FormItem</span><br><span class="line"> {...formItemLayout}</span><br><span class="line"> label="地址"</span><br><span class="line"> ></span><br><span class="line"> {getFieldDecorator('address', {</span><br><span class="line"> initialValue: currentItem.address,</span><br><span class="line"> rules: [{</span><br><span class="line"> required: true, message: 'Please input your address!',</span><br><span class="line"> }],</span><br><span class="line"> })(</span><br><span class="line"> <Input placeholder="请输入地址" /></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br><span class="line"> </Form></span><br><span class="line"> </Modal></span><br><span class="line"> </div></span><br><span class="line"> )</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default (Form.create({})(UserModal));</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">主要代码:destroyOnClose={true}</span><br><span class="line"><Modal</span><br><span class="line"> title="编辑"</span><br><span class="line"> visible={visible}</span><br><span class="line"> onOk={() => handleOk()}</span><br><span class="line"> onCancel={() => handleCancel()}</span><br><span class="line"> destroyOnClose={true}</span><br><span class="line"> ></span><br><span class="line">...</span><br><span class="line"></Modal></span><br></pre></td></tr></table></figure> <h3 id="16-2-方法二"><a href="#16-2-方法二" class="headerlink" title="16.2 方法二"></a>16.2 方法二</h3><p>如果是 class 类,可以使用钩子。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">componentDidUpdate = (prevProps, prevState) => {</span><br><span class="line"> if (!prevProps.visible) {</span><br><span class="line"> prevProps.form.resetFields();</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure> <p>代码参考:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br></pre></td><td class="code"><pre><span class="line">import React from 'react';</span><br><span class="line">import { Input, Modal, Form } from 'antd';</span><br><span class="line">import styles from './UserModal.less';</span><br><span class="line"></span><br><span class="line">const FormItem = Form.Item;</span><br><span class="line"></span><br><span class="line">@Form.create({})</span><br><span class="line">class UserModal extends React.PureComponent {</span><br><span class="line"></span><br><span class="line"> componentDidUpdate = (prevProps, prevState) => {</span><br><span class="line"> if (!prevProps.visible) {</span><br><span class="line"> prevProps.form.resetFields();</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> handleOk = () => {</span><br><span class="line"> const { dispatch, form } = this.props;</span><br><span class="line"> form.validateFields((err, fieldsValue) => {</span><br><span class="line"> if (err) return;</span><br><span class="line"> dispatch({</span><br><span class="line"> type: 'demo/update',</span><br><span class="line"> payload: {</span><br><span class="line"> currentItem: fieldsValue</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> handleCancel = () => {</span><br><span class="line"> const { dispatch } = this.props;</span><br><span class="line"> dispatch({</span><br><span class="line"> type: 'demo/hideModal'</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> const { currentItem, form, visible } = this.props;</span><br><span class="line"> const formItemLayout = {</span><br><span class="line"> labelCol: {</span><br><span class="line"> xs: { span: 24 },</span><br><span class="line"> sm: { span: 4 },</span><br><span class="line"> },</span><br><span class="line"> wrapperCol: {</span><br><span class="line"> xs: { span: 24 },</span><br><span class="line"> sm: { span: 20 },</span><br><span class="line"> },</span><br><span class="line"> };</span><br><span class="line"> const { getFieldDecorator } = form;</span><br><span class="line"> return (</span><br><span class="line"> <div className={styles.root}></span><br><span class="line"> <Modal</span><br><span class="line"> title="编辑"</span><br><span class="line"> visible={visible}</span><br><span class="line"> onOk={() => this.handleOk()}</span><br><span class="line"> onCancel={() => this.handleCancel()}</span><br><span class="line"> ></span><br><span class="line"> <Form></span><br><span class="line"> <FormItem</span><br><span class="line"> {...formItemLayout}</span><br><span class="line"> label="用户名"</span><br><span class="line"> ></span><br><span class="line"> {getFieldDecorator('name', {</span><br><span class="line"> initialValue: currentItem.name,</span><br><span class="line"> rules: [{</span><br><span class="line"> required: true, message: 'Please input your name!',</span><br><span class="line"> }],</span><br><span class="line"> })(</span><br><span class="line"> <Input placeholder="请输入用户名" /></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br><span class="line"> <FormItem</span><br><span class="line"> {...formItemLayout}</span><br><span class="line"> label="年龄"</span><br><span class="line"> ></span><br><span class="line"> {getFieldDecorator('age', {</span><br><span class="line"> initialValue: currentItem.age,</span><br><span class="line"> rules: [{</span><br><span class="line"> required: true, message: 'Please input your age!',</span><br><span class="line"> }],</span><br><span class="line"> })(</span><br><span class="line"> <Input placeholder="请输入年龄" /></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br><span class="line"> <FormItem</span><br><span class="line"> {...formItemLayout}</span><br><span class="line"> label="地址"</span><br><span class="line"> ></span><br><span class="line"> {getFieldDecorator('address', {</span><br><span class="line"> initialValue: currentItem.address,</span><br><span class="line"> rules: [{</span><br><span class="line"> required: true, message: 'Please input your address!',</span><br><span class="line"> }],</span><br><span class="line"> })(</span><br><span class="line"> <Input placeholder="请输入地址" /></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br><span class="line"> </Form></span><br><span class="line"> </Modal></span><br><span class="line"> </div></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default UserModal;</span><br><span class="line">// export default (Form.create({})(UserModal));</span><br></pre></td></tr></table></figure> <h2 id="17-Modal-是否显示-footer-底部按钮"><a href="#17-Modal-是否显示-footer-底部按钮" class="headerlink" title="17 Modal 是否显示 footer 底部按钮"></a>17 Modal 是否显示 footer 底部按钮</h2><p>一般应用场景,详情不需要底部按钮,新增和修改需要。<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82NjMwMjY1LWMwNjAwNDJmZWQ3OWE3MmMucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXB8aW1hZ2VWaWV3Mi8yL3cvMTIwMC9mb3JtYXQvd2VicA?x-oss-process=image/format,png" alt="image"></p> <p>api</p> <p><strong>解决:</strong></p> <p>通过父组件传递一个空的字符串或者 {footer: null} 给 Modal 组件进行属性解构。</p> <p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82NjMwMjY1LWE5OTcyZWQxNjhkY2MwYTQucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXB8aW1hZ2VWaWV3Mi8yL3cvNzU3L2Zvcm1hdC93ZWJw?x-oss-process=image/format,png" alt="image"></p> <p>父组件需要传入的值</p> <p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82NjMwMjY1LTc4YmY4NDZlOGZjNTFjOWIucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXB8aW1hZ2VWaWV3Mi8yL3cvMTIwMC9mb3JtYXQvd2VicA?x-oss-process=image/format,png" alt="image"></p> <p>子组件 Modal</p> <h2 id="18-有-connect-和-Form-表单"><a href="#18-有-connect-和-Form-表单" class="headerlink" title="18 有 connect 和 Form 表单"></a>18 有 connect 和 Form 表单</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">function mapStateToProps({ onlineCamera }) {</span><br><span class="line"> return {</span><br><span class="line"> favorites: onlineCamera.favorites,</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default connect(mapStateToProps)(Form.create()(TreeModal));</span><br></pre></td></tr></table></figure> <h2 id="19-Tree-树组件增加右键菜单"><a href="#19-Tree-树组件增加右键菜单" class="headerlink" title="19 Tree 树组件增加右键菜单"></a>19 Tree 树组件增加右键菜单</h2><p>参考:<br><a target="_blank" rel="noopener" href="https://github.com/ant-design/ant-design/issues/5151">https://github.com/ant-design/ant-design/issues/5151</a></p> <p>关键代码:</p> <blockquote> <Tree onRightClick={this.treeNodeonRightClick} > </blockquote> <p>// 实现这个方法 treeNodeonRightClick</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">treeNodeonRightClick(e) {</span><br><span class="line"> this.setState({</span><br><span class="line"> rightClickNodeTreeItem: {</span><br><span class="line"> pageX: e.event.pageX,</span><br><span class="line"> pageY: e.event.pageY,</span><br><span class="line"> id: e.node.props['data-key'],</span><br><span class="line"> categoryName: e.node.props['data-title']</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br></pre></td></tr></table></figure> <p>// id 和 categoryName 是生成时绑上去的</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><TreeNode</span><br><span class="line"> key={item.id}</span><br><span class="line"> title={title}</span><br><span class="line"> data-key={item.id}</span><br><span class="line"> data-title={item.categoryName}</span><br><span class="line"> />);</span><br><span class="line"></span><br></pre></td></tr></table></figure> <p>// 最后绑个菜单就可以实现了</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">getNodeTreeRightClickMenu() {</span><br><span class="line"> const {pageX, pageY} = {...this.state.rightClickNodeTreeItem};</span><br><span class="line"> const tmpStyle = {</span><br><span class="line"> position: 'absolute',</span><br><span class="line"> left: `${pageX - 220}px`,</span><br><span class="line"> top: `${pageY - 70}px`</span><br><span class="line"> };</span><br><span class="line"> const menu = (</span><br><span class="line"> <Menu</span><br><span class="line"> onClick={this.handleMenuClick}</span><br><span class="line"> style={tmpStyle}</span><br><span class="line"> className={style.categs_tree_rightmenu}</span><br><span class="line"> ></span><br><span class="line"> <Menu.Item key='1'><Icon type='plus-circle'/>{'加同级'}</Menu.Item></span><br><span class="line"> <Menu.Item key='2'><Icon type='plus-circle-o'/>{'加下级'}</Menu.Item></span><br><span class="line"> <Menu.Item key='4'><Icon type='edit'/>{'修改'}</Menu.Item></span><br><span class="line"> <Menu.Item key='3'><Icon type='minus-circle-o'/>{'删除目录'}</Menu.Item></span><br><span class="line"> </Menu></span><br><span class="line"> );</span><br><span class="line"> return (this.state.rightClickNodeTreeItem == null) ? '' : menu;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure> <p>getNodeTreeRightClickMenu 方法放在 render 中:</p> <p>getNodeTreeRightClickMenu 方法是放在生成主界面的方法里( render ),因为每一次 state 的变化后,render 方法都会执行,所以变一下任意的 this.state 里面的状态,就会执行 render 方法 ,这样 getNodeTreeRightClickMenu 方法放在 render 方法里来生成界面的一部分。就可以了</p> <p><strong>项目中实现关键代码:</strong></p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line"> * @Author: lin.zehong</span><br><span class="line"> * @Date: 2018-12-02 22:13:59</span><br><span class="line"> * @Last Modified by: lin.zehong</span><br><span class="line"> * @Last Modified time: 2018-12-19 16:36:27</span><br><span class="line"> * @Desc: 收藏夹--树</span><br><span class="line"> */</span><br><span class="line">import React from 'react';</span><br><span class="line">import { connect } from 'dva';</span><br><span class="line">import { Tree, Menu } from 'antd';</span><br><span class="line">import Zcon from 'zteui-icon';</span><br><span class="line">import styles from './TreeCollect.less';</span><br><span class="line"></span><br><span class="line">const { TreeNode } = Tree;</span><br><span class="line"></span><br><span class="line">class TreeCollect extends React.Component {</span><br><span class="line"> state = {</span><br><span class="line"> expandedKeys: ['-1'],</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 树节点右键事件</span><br><span class="line"> treeNodeonRightClick = ({ event, node }) => {</span><br><span class="line"> event.persist();</span><br><span class="line"> const { offsetLeft, _isCollapsed } = this.props;</span><br><span class="line"> const menuWidth = _isCollapsed ? 80 : 200;</span><br><span class="line"> const { favorites, favoritesDetail } = node.props;</span><br><span class="line"> this.changefavorites(favorites);</span><br><span class="line"> const hasChild = !!(favorites && favorites.scjId); // 收藏夹</span><br><span class="line"> this.setState({</span><br><span class="line"> rightClickNodeTreeItem: {</span><br><span class="line"> pageX: event.pageX - offsetLeft - 16 - menuWidth,</span><br><span class="line"> pageY: event.target.offsetTop + 28,</span><br><span class="line"> key: node.props.eventKey,</span><br><span class="line"> id: node.props.eventKey,</span><br><span class="line"> title: node.props.title,</span><br><span class="line"> favorites,</span><br><span class="line"> favoritesDetail,</span><br><span class="line"> hasChild,</span><br><span class="line"> },</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 右键节点页面展示</span><br><span class="line"> getNodeTreeRightClickMenu = () => {</span><br><span class="line"> const { rightClickNodeTreeItem } = this.state;</span><br><span class="line"> const { pageX, pageY, hasChild, key } = { ...rightClickNodeTreeItem };</span><br><span class="line"> const tmpStyle = {</span><br><span class="line"> position: 'absolute',</span><br><span class="line"> left: `${pageX}px`,</span><br><span class="line"> top: `${pageY}px`,</span><br><span class="line"> boxShadow: '2px 2px 10px #333333',</span><br><span class="line"> };</span><br><span class="line"> const menuHasNode = (</span><br><span class="line"> <Menu</span><br><span class="line"> onClick={this.handleMenuClick}</span><br><span class="line"> style={tmpStyle}</span><br><span class="line"> className={styles.categs_tree_rightmenu}</span><br><span class="line"> ></span><br><span class="line"> <Menu.Item key='1'>自动巡查</Menu.Item></span><br><span class="line"> <Menu.Item key='2'>重命名</Menu.Item></span><br><span class="line"> <Menu.Item key='3'>添加同级目录</Menu.Item></span><br><span class="line"> <Menu.Item key='4'>添加子目录</Menu.Item></span><br><span class="line"> <Menu.Item key='5'>删除</Menu.Item></span><br><span class="line"> </Menu></span><br><span class="line"> );</span><br><span class="line"> const menuRoot = (</span><br><span class="line"> <Menu</span><br><span class="line"> onClick={this.handleMenuClick}</span><br><span class="line"> style={tmpStyle}</span><br><span class="line"> className={styles.categs_tree_rightmenu}</span><br><span class="line"> ></span><br><span class="line"> <Menu.Item key='1'>自动巡查</Menu.Item></span><br><span class="line"> <Menu.Item key='2'>重命名</Menu.Item></span><br><span class="line"> <Menu.Item key='4'>添加子目录</Menu.Item></span><br><span class="line"> </Menu></span><br><span class="line"> );</span><br><span class="line"> const menuNoNode = (</span><br><span class="line"> <Menu</span><br><span class="line"> onClick={this.handleMenuClick}</span><br><span class="line"> style={tmpStyle}</span><br><span class="line"> className={styles.categs_tree_rightmenu}</span><br><span class="line"> ></span><br><span class="line"> <Menu.Item key='6'>取消收藏</Menu.Item></span><br><span class="line"> </Menu></span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> const menu = hasChild ? (key === "-1" ? menuRoot : menuHasNode) : menuNoNode;</span><br><span class="line"></span><br><span class="line"> return (rightClickNodeTreeItem == null) ? '' : menu;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 隐藏右键菜单</span><br><span class="line"> hideTreeRight = () => {</span><br><span class="line"> this.setState({ rightClickNodeTreeItem: null });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> const { expandedKeys, selectedKeys } = this.state;</span><br><span class="line"> const { isExpand, gData } = this.props;</span><br><span class="line"> const loop = data => data.map((item) => {</span><br><span class="line"> if (item.children && item.favorites) {</span><br><span class="line"> return <TreeNode key={item.key} icon={<Zcon type="thing" />} title={item.title} favorites={item.favorites}>{loop(item.children)}</TreeNode>;</span><br><span class="line"> }</span><br><span class="line"> return <TreeNode key={item.favoritesDetail.sxtxxId} title={item.title} favoritesDetail={item.favoritesDetail} />;</span><br><span class="line"> });</span><br><span class="line"> return (</span><br><span class="line"> <div className={`${styles.root} ${isExpand ? '' : styles.hideTree}`} onClick={() => this.hideTreeRight()}></span><br><span class="line"> <Tree</span><br><span class="line"> showIcon</span><br><span class="line"> className="draggable-tree"</span><br><span class="line"> defaultExpandedKeys={expandedKeys}</span><br><span class="line"> selectedKeys={selectedKeys}</span><br><span class="line"> onRightClick={this.treeNodeonRightClick}</span><br><span class="line"> onSelect={this.onSelect}</span><br><span class="line"> ></span><br><span class="line"> {loop(gData)}</span><br><span class="line"> </Tree></span><br><span class="line"> {this.getNodeTreeRightClickMenu()}</span><br><span class="line"> </div></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function mapStateToProps({ onlineCamera, publicModel }) {</span><br><span class="line"> return {</span><br><span class="line"> gData: onlineCamera.collectTree,</span><br><span class="line"> cameraNum: onlineCamera.cameraNum,</span><br><span class="line"> inspectionCamera: onlineCamera.inspectionCamera,</span><br><span class="line"> _isCollapsed: publicModel._isCollapsed,</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default connect(mapStateToProps)(TreeCollect);</span><br></pre></td></tr></table></figure> <h2 id="20-直接使用-rc-form-库-createForm,与-antd-Form-的-Form-create-设置样式不同"><a href="#20-直接使用-rc-form-库-createForm,与-antd-Form-的-Form-create-设置样式不同" class="headerlink" title="20 直接使用 rc-form 库 createForm,与 antd Form 的 Form.create() 设置样式不同"></a>20 直接使用 rc-form 库 createForm,与 antd Form 的 Form.create() 设置样式不同</h2><ul> <li>使用 antd Form 的 Form.create()</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">import React from 'react'</span><br><span class="line">import PropTypes from 'prop-types'</span><br><span class="line">import { Form, Button } from 'antd'</span><br><span class="line"></span><br><span class="line">class BalloonContent extends React.Component {</span><br><span class="line"> render() {</span><br><span class="line"> const { form } = this.props;</span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> <Form</span><br><span class="line"> size='medium'</span><br><span class="line"> className={Styles.wrapForm}</span><br><span class="line"> ></span><br><span class="line"> <Form.Item</span><br><span class="line"> label="算子输出"</span><br><span class="line"> ></span><br><span class="line"> {form.getFieldDecorator('stdioOutput', {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> required: true,</span><br><span class="line"> message: '输出不能为空',</span><br><span class="line"> },</span><br><span class="line"> ],</span><br><span class="line"> })(<Input />)}</span><br><span class="line"> </Form.Item></span><br><span class="line"> </Form></span><br><span class="line"> </div></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default Form.create()(BalloonContent) // !!!</span><br></pre></td></tr></table></figure> <p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82NjMwMjY1LTVhZDlkYTIxNGY3YWZmZTEucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXB8aW1hZ2VWaWV3Mi8yL3cvMzQ1L2Zvcm1hdC93ZWJw?x-oss-process=image/format,png" alt="image"></p> <p>结果样式</p> <ul> <li>直接使用 rc-form 库 createForm</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">import React from 'react'</span><br><span class="line">import PropTypes from 'prop-types'</span><br><span class="line">import { Form, Button } from 'antd'</span><br><span class="line">import { createForm } from 'rc-form'</span><br><span class="line"></span><br><span class="line">class BalloonContent extends React.Component {</span><br><span class="line"> render() {</span><br><span class="line"> const { form } = this.props;</span><br><span class="line"> const { getFieldDecorator, getFieldError } = form ;</span><br><span class="line"> const stdioOutputError = getFieldError('stdioOutput'); // !!!</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> <Form</span><br><span class="line"> size='medium'</span><br><span class="line"> className={Styles.wrapForm}</span><br><span class="line"> ></span><br><span class="line"> <Form.Item</span><br><span class="line"> label="算子输出"</span><br><span class="line"> required // !!!</span><br><span class="line"> validateState={stdioOutputError ? 'error' : 'success'} // !!!</span><br><span class="line"> help={stdioOutputError} // !!!</span><br><span class="line"> ></span><br><span class="line"> {form.getFieldDecorator('stdioOutput', {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> required: true,</span><br><span class="line"> message: '输出不能为空',</span><br><span class="line"> },</span><br><span class="line"> ],</span><br><span class="line"> })(<Input />)}</span><br><span class="line"> </Form.Item></span><br><span class="line"> </Form></span><br><span class="line"> </div></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default createForm ()(BalloonContent) // !!!</span><br></pre></td></tr></table></figure> <p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82NjMwMjY1LWRhOTIwYzUzMzk4N2FkZDIucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXB8aW1hZ2VWaWV3Mi8yL3cvMzQyL2Zvcm1hdC93ZWJw?x-oss-process=image/format,png" alt="image"></p> <p>结果</p> <h2 id="21-Form-表单实时校验-debounce-应用,和不同字段相互校验影响"><a href="#21-Form-表单实时校验-debounce-应用,和不同字段相互校验影响" class="headerlink" title="21 Form 表单实时校验 _.debounce 应用,和不同字段相互校验影响"></a>21 Form 表单实时校验 _.debounce 应用,和不同字段相互校验影响</h2><p>例如:表单字段,密码和确认密码,改变 Password,如果与 Confirm Password 不一致,也会在 Confirm Password 做提示:</p> <p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82NjMwMjY1LWEzZDhhNDljYzdlMGY0ZWIucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXB8aW1hZ2VWaWV3Mi8yL3cvNjgwL2Zvcm1hdC93ZWJw?x-oss-process=image/format,png" alt="image"></p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">官网示例:注册新用户,主要代码</span><br><span class="line"></span><br><span class="line">compareToFirstPassword = (rule, value, callback) => {</span><br><span class="line"> const { form } = this.props;</span><br><span class="line"> if (value && value !== form.getFieldValue('password')) {</span><br><span class="line"> callback('Two passwords that you enter is inconsistent!');</span><br><span class="line"> } else {</span><br><span class="line"> callback();</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">validateToNextPassword = (rule, value, callback) => {</span><br><span class="line"> const { form } = this.props;</span><br><span class="line"> if (value && this.state.confirmDirty) {</span><br><span class="line"> form.validateFields(['confirm'], { force: true });</span><br><span class="line"> }</span><br><span class="line"> callback();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><Form.Item label="Password" hasFeedback></span><br><span class="line"> {getFieldDecorator('password', {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> required: true,</span><br><span class="line"> message: 'Please input your password!',</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> validator: this.validateToNextPassword,</span><br><span class="line"> },</span><br><span class="line"> ],</span><br><span class="line">})(<Input.Password />)}</span><br><span class="line"></Form.Item></span><br><span class="line"></span><br><span class="line"><Form.Item label="Confirm Password" hasFeedback></span><br><span class="line"> {getFieldDecorator('confirm', {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> required: true,</span><br><span class="line"> message: 'Please confirm your password!',</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> validator: this.compareToFirstPassword,</span><br><span class="line"> },</span><br><span class="line"> ],</span><br><span class="line">})(<Input.Password onBlur={this.handleConfirmBlur} />)}</span><br><span class="line"></Form.Item></span><br></pre></td></tr></table></figure> <p>实际项目例子,选择所属数据库,校验表名:</p> <p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82NjMwMjY1LTFiZWYyNjBiMzY0NTkwYjQucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXB8aW1hZ2VWaWV3Mi8yL3cvNTgxL2Zvcm1hdC93ZWJw?x-oss-process=image/format,png" alt="image"></p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line">主要代码:</span><br><span class="line"></span><br><span class="line">import _ from "lodash";</span><br><span class="line"></span><br><span class="line">// 写入新表,选择数据库,需要校验已有的表名</span><br><span class="line">validateToTableName = (rule, value, callback) => {</span><br><span class="line"> const { form: { getFieldValue, validateFields }} = this.props;</span><br><span class="line"> const targetTableCode = getFieldValue("targetTableCode");</span><br><span class="line"> if (targetTableCode) {</span><br><span class="line"> validateFields(['targetTableCode'], { force: true });</span><br><span class="line"> }</span><br><span class="line"> callback();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">// 写入新表,校验表名</span><br><span class="line">// eslint-disable-next-line</span><br><span class="line">validateTableExist = _.debounce((rule, value, callback) => {</span><br><span class="line"> const { form: { getFieldValue }, dispatch } = this.props;</span><br><span class="line"> const targetDataSource = getFieldValue("targetDataSource");</span><br><span class="line"> const targetTableCode = getFieldValue("targetTableCode");</span><br><span class="line"> dispatch({</span><br><span class="line"> type: "applyDetail/tableExist",</span><br><span class="line"> payload: {</span><br><span class="line"> dataSourceCode: targetDataSource,</span><br><span class="line"> table: targetTableCode,</span><br><span class="line"> },</span><br><span class="line"> }).then(result => {</span><br><span class="line"> if (result) {</span><br><span class="line"> callback("该表名已存在");</span><br><span class="line"> } else {</span><br><span class="line"> callback();</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line">}, 500);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><Form.Item label="所属数据库"></span><br><span class="line"> {getFieldDecorator("targetDataSource", {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> required: true,</span><br><span class="line"> message: "请选择所属数据库",</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> validator: this.validateToTableName, // !!!</span><br><span class="line"> },</span><br><span class="line"> ],</span><br><span class="line"> initialValue:</span><br><span class="line"> exchangeFormat.targetDataSource ||</span><br><span class="line"> (dataSourceList.length > 0</span><br><span class="line"> ? dataSourceList[0].code</span><br><span class="line"> : undefined),</span><br><span class="line"> })(dataBaseComponent({ className: styles.formInput }))}</span><br><span class="line"></Form.Item></span><br><span class="line"></span><br><span class="line"><Form.Item label="表名"></span><br><span class="line"> {getFieldDecorator("targetTableCode", {</span><br><span class="line"> rules: [</span><br><span class="line"> {</span><br><span class="line"> required: true,</span><br><span class="line"> message: "请输入新表表名",</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> pattern: checkBackEndTableName,</span><br><span class="line"> message: "只支持英文字母、数字、英文格式、下划线",</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> validator: this.validateTableExist, // !!!</span><br><span class="line"> },</span><br><span class="line"> ],</span><br><span class="line"> initialValue:</span><br><span class="line"> (exchangeFormat.formatType === WRITE_IN_NEW_TABLE</span><br><span class="line"> ? exchangeFormat.targetTableCode</span><br><span class="line"> : undefined) || undefined,</span><br><span class="line"> })(</span><br><span class="line"> <Input</span><br><span class="line"> className={styles.formInput}</span><br><span class="line"> disabled={disabled}</span><br><span class="line"> placeholder="请输入"</span><br><span class="line"> /></span><br><span class="line"> )}</span><br><span class="line"></Form.Item></span><br></pre></td></tr></table></figure> <h2 id="22-Form-组件方法-getFieldsValue-获取自定义组件的值"><a href="#22-Form-组件方法-getFieldsValue-获取自定义组件的值" class="headerlink" title="22 Form 组件方法 getFieldsValue 获取自定义组件的值"></a>22 Form 组件方法 getFieldsValue 获取自定义组件的值</h2><p>项目实例:对 antd RangePicker 抽取完独立组件后,form 表单获取不到值</p> <p>自定义组件被 getFieldsValue 包裹,会获得以下属性:</p> <p>onChange 方法, 子组件调用此方法,可将值传给父组件,从而 Form 可拿到自定义组件的值 value 属性,获得初始值</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><Form.Item label="发送时间"></span><br><span class="line"> {getFieldDecorator('range-time-picker', {</span><br><span class="line"> rules: [{ required: false, message: '请输入开始时间-结束时间' }],</span><br><span class="line"> })(</span><br><span class="line"> <RangePickerPage /></span><br><span class="line"> )}</span><br><span class="line"></Form.Item></span><br></pre></td></tr></table></figure> <p>下面是对 antd RangePicker 进行封装,通过组件 RangePicker 本身的 onChange 方法,调用 this.props.onChange(子组件不用传 onChange 方法,自定义组件被 getFieldsValue 包裹,会自动获取 onChage 属性),则通过 form.validateFields 可以获取到值。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line"> * Author: lin.zehong</span><br><span class="line"> * Date: 2019-10-04 09:14:52</span><br><span class="line"> * Last Modified by: lin.zehong</span><br><span class="line"> * Last Modified time: 2019-10-04 09:14:52</span><br><span class="line"> * Desc: 对 antd RangePicker 进行封装</span><br><span class="line"> */</span><br><span class="line">import React from "react";</span><br><span class="line">import moment from "moment";</span><br><span class="line">import { DatePicker } from "antd";</span><br><span class="line"></span><br><span class="line">const { RangePicker } = DatePicker;</span><br><span class="line"></span><br><span class="line">class RangePickerPage extends React.Component {</span><br><span class="line"></span><br><span class="line"> range = (start, end) => {</span><br><span class="line"> const result = [];</span><br><span class="line"> for (let i = start; i < end; i += 1) {</span><br><span class="line"> result.push(i);</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> disabledDate = (current) => {</span><br><span class="line"> // Can not select days before today and today</span><br><span class="line"> return current && current < moment().endOf('day');</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> disabledRangeTime = (_, type) => {</span><br><span class="line"> if (type === 'start') {</span><br><span class="line"> return {</span><br><span class="line"> disabledHours: () => this.range(0, 60).splice(4, 20),</span><br><span class="line"> disabledMinutes: () => this.range(30, 60),</span><br><span class="line"> disabledSeconds: () => [55, 56],</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> return {</span><br><span class="line"> disabledHours: () => this.range(0, 60).splice(20, 4),</span><br><span class="line"> disabledMinutes: () => this.range(0, 31),</span><br><span class="line"> disabledSeconds: () => [55, 56],</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> onChange = (dates, dateStrings) => {</span><br><span class="line"> const { onChange } = this.props; // !!!</span><br><span class="line"> onChange(dateStrings);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> return (</span><br><span class="line"> <RangePicker</span><br><span class="line"> allowClear</span><br><span class="line"> disabledDate={this.disabledDate}</span><br><span class="line"> disabledTime={this.disabledRangeTime}</span><br><span class="line"> showTime={{</span><br><span class="line"> hideDisabledOptions: true,</span><br><span class="line"> defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('11:59:59', 'HH:mm:ss')],</span><br><span class="line"> }}</span><br><span class="line"> format="YYYY-MM-DD HH:mm:ss"</span><br><span class="line"> onChange={this.onChange} // !!!</span><br><span class="line"> /></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default RangePickerPage;</span><br></pre></td></tr></table></figure> <p>参考:<a target="_blank" rel="noopener" href="https://juejin.im/post/5c9c6c08e51d4503e514eaac">https://juejin.im/post/5c9c6c08e51d4503e514eaac</a></p> <h2 id="23-Form-表单清空和重置的区别以及方法"><a href="#23-Form-表单清空和重置的区别以及方法" class="headerlink" title="23 Form 表单清空和重置的区别以及方法"></a>23 Form 表单清空和重置的区别以及方法</h2><p>这里首先需要明确,清空和重置是不同的概念,清空是把内容都清空掉,而重置是恢复 form 表单初始值。</p> <p>例如:新增功能,清空和重置就是一样的效果,而对于编辑,清空就是把初始值都清空掉,重置就是恢复刚开始的初始值。</p> <ul> <li>清空</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">form.setFieldsValue({"fieldName": ""});</span><br></pre></td></tr></table></figure> <ul> <li>重置</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">form.resetFields();</span><br></pre></td></tr></table></figure> <h2 id="24-DatePicker-组件,部分日期-x2F-时间为可选"><a href="#24-DatePicker-组件,部分日期-x2F-时间为可选" class="headerlink" title="24 DatePicker 组件,部分日期/时间为可选"></a>24 DatePicker 组件,部分日期/时间为可选</h2><h3 id="24-1-不能选择今天之前的日期,包括今天的日期也不可以选择"><a href="#24-1-不能选择今天之前的日期,包括今天的日期也不可以选择" class="headerlink" title="24.1 不能选择今天之前的日期,包括今天的日期也不可以选择"></a>24.1 不能选择今天之前的日期,包括今天的日期也不可以选择</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">const disabledDate = (current) => {</span><br><span class="line"> return current && current < moment().endOf('day');</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h3 id="24-2-不能选择今天之前的日期,今天日期可以选择"><a href="#24-2-不能选择今天之前的日期,今天日期可以选择" class="headerlink" title="24.2 不能选择今天之前的日期,今天日期可以选择"></a>24.2 不能选择今天之前的日期,今天日期可以选择</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">const disabledDate = (current) => {</span><br><span class="line"> return current && current < moment().subtract(1, 'day');</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h3 id="24-3-当前时间之后的时间点,精确到小时"><a href="#24-3-当前时间之后的时间点,精确到小时" class="headerlink" title="24.3 当前时间之后的时间点,精确到小时"></a>24.3 当前时间之后的时间点,精确到小时</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">const [upgradeTime, setUpgradeTime] = useState(moment('00:00:00', 'HH:mm:ss'))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">const disabledDate = (current) => {</span><br><span class="line"> return current && current < moment().subtract(1, 'day'); // 今天可以选择</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">const disabledDateTime = () => {</span><br><span class="line"> const hours = moment().hours(); // 0~23</span><br><span class="line"> // 当日只能选择当前时间之后的时间点</span><br><span class="line"> if (upgradeTime.date() === moment().date()) {</span><br><span class="line"> return {</span><br><span class="line"> disabledHours: () => range(0, hours + 1),</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><Form.Item label="发送时间"></span><br><span class="line"> {getFieldDecorator('pushTime', {</span><br><span class="line"> rules: [{ required: false, message: '请输入发送时间' }],</span><br><span class="line"> initialValue: record.pushType === 0 ? null :</span><br><span class="line"> (record.pushTime ? moment(record.pushTime, 'YYYY-MM-DD HH:mm:ss') : null), // 定时发送才显示时间</span><br><span class="line"> })(</span><br><span class="line"> <DatePicker</span><br><span class="line"> format="YYYY-MM-DD HH:mm:ss"</span><br><span class="line"> disabledDate={disabledDate}</span><br><span class="line"> disabledTime={disabledDateTime}</span><br><span class="line"> style={{ width: "100%" }}</span><br><span class="line"></span><br><span class="line"> onChange={(timer) => setUpgradeTime(timer)} // !!!</span><br><span class="line"> showTime={{ defaultValue: moment(upgradeTime) }} // !!!</span><br><span class="line"> />,</span><br><span class="line"> )}</span><br><span class="line"></Form.Item></span><br></pre></td></tr></table></figure> <h3 id="24-4-当前时间之后的时间点,精确到分"><a href="#24-4-当前时间之后的时间点,精确到分" class="headerlink" title="24.4 当前时间之后的时间点,精确到分"></a>24.4 当前时间之后的时间点,精确到分</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">const [upgradeTime, setUpgradeTime] = useState(moment('00:00:00', 'HH:mm:ss'))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">const disabledDate = (current) => {</span><br><span class="line"> return current && current < moment().subtract(1, 'day'); // 今天可以选择</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">const disabledDateTime = () => {</span><br><span class="line"> const hours = moment().hours(); // 0~23</span><br><span class="line"> const minutes = moment().minutes(); // 0~59</span><br><span class="line"> // 当日只能选择当前时间之后的时间点</span><br><span class="line"> if (upgradeTime.date() === moment().date()) {</span><br><span class="line"> return {</span><br><span class="line"> disabledHours: () => range(0, hours),</span><br><span class="line"> disabledMinutes: () => range(0, minutes), // 精确到分</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><Form.Item label="发送时间"></span><br><span class="line"> {getFieldDecorator('pushTime', {</span><br><span class="line"> rules: [{ required: false, message: '请输入发送时间' }],</span><br><span class="line"> initialValue: record.pushType === 0 ? null :</span><br><span class="line"> (record.pushTime ? moment(record.pushTime, 'YYYY-MM-DD HH:mm:ss') : null), // 定时发送才显示时间</span><br><span class="line"> })(</span><br><span class="line"> <DatePicker</span><br><span class="line"> format="YYYY-MM-DD HH:mm:ss"</span><br><span class="line"> disabledDate={disabledDate}</span><br><span class="line"> disabledTime={disabledDateTime}</span><br><span class="line"> style={{ width: "100%" }}</span><br><span class="line"></span><br><span class="line"> onChange={(timer) => setUpgradeTime(timer)} // !!!</span><br><span class="line"> showTime={{ defaultValue: moment(upgradeTime) }} // !!!</span><br><span class="line"> />,</span><br><span class="line"> )}</span><br><span class="line"></Form.Item></span><br></pre></td></tr></table></figure> </div> <footer class="post-footer"> <div class="post-eof"></div> </footer> </article> <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN"> <link itemprop="mainEntityOfPage" href="http://example.com/2022/12/16/Rxjs%E5%B8%B8%E8%A7%81%E6%93%8D%E4%BD%9C%E7%AC%A6/"> <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person"> <meta itemprop="image" content="/images/portal.jpg"> <meta itemprop="name" content="Bruce Chen"> <meta itemprop="description" content="It's better to burn out than to fade away."> </span> <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"> <meta itemprop="name" content="Bruce Chen's Blog"> </span> <header class="post-header"> <h2 class="post-title" itemprop="name headline"> <a href="/2022/12/16/Rxjs%E5%B8%B8%E8%A7%81%E6%93%8D%E4%BD%9C%E7%AC%A6/" class="post-title-link" itemprop="url">Rxjs常见操作符</a> </h2> <div class="post-meta"> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-calendar"></i> </span> <span class="post-meta-item-text">发表于</span> <time title="创建时间:2022-12-16 14:45:42 / 修改时间:14:51:47" itemprop="dateCreated datePublished" datetime="2022-12-16T14:45:42+08:00">2022-12-16</time> </span> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-folder"></i> </span> <span class="post-meta-item-text">分类于</span> <span itemprop="about" itemscope itemtype="http://schema.org/Thing"> <a href="/categories/%E5%89%8D%E7%AB%AFUI%E6%A1%86%E6%9E%B6/" itemprop="url" rel="index"><span itemprop="name">前端UI框架</span></a> </span> </span> </div> </header> <div class="post-body" itemprop="articleBody"> <h2 id="一-RxJS-初试"><a href="#一-RxJS-初试" class="headerlink" title="一.RxJS 初试"></a>一.RxJS 初试</h2><ul> <li>在 javascript 中的试炼</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">const height = document.getElementById('height');</span><br><span class="line">const height$ = Rx.Observable.fromEvent(height, 'keyup');//$是约定俗称的命名方式,因为通过Rx.Observable已经将其转换成了流形式,其中含义是:将keyup事件转换成了Observable观察者</span><br><span class="line">//既然是观察者,则可以订阅</span><br><span class="line">height$.subscribe(val => console.log(val));//当输入的时候会输出事件的对象,获得值则使用val.target.value</span><br><span class="line">// 输入是1,console打印的就是1</span><br></pre></td></tr></table></figure> <ul> <li>RxJs 的威力在于合并和转换流,实例如下</li> </ul> <p><strong>(1)html</strong></p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><input type="text" id="length"/></span><br><span class="line"><input type="text" id="width" /></span><br><span class="line"><div id="area"></div></span><br></pre></td></tr></table></figure> <p><strong>(2)js</strong></p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">const length = document.getElementById('height');</span><br><span class="line">const width = document.getElementById('width');</span><br><span class="line">const area = document.getElementById('area');</span><br><span class="line">const length$ = Rx.Observable.fromEvent(length, 'keyup').pluck('target','value');</span><br><span class="line">const width$ = Rx.Observable.fromEvent(width, 'keyup').pluck('target','value');</span><br><span class="line">//combineLatest表示如果监听的两个值有一个改变则会重新计算一遍</span><br><span class="line">const area$ = Rx.Observable.combineLatest(length$, width$, (l,w)=>{return l*w});//用于将两个流合并成一个数据流</span><br><span class="line">area$.subscribe(val => area.innerHTML = val);</span><br></pre></td></tr></table></figure> <ul> <li>事件流:理解 Rx 的关键是要把任何变化想象成事件流</li> </ul> <h2 id="二-RxJS-常见操作符"><a href="#二-RxJS-常见操作符" class="headerlink" title="二.RxJS 常见操作符"></a>二.RxJS 常见操作符</h2><h3 id="1-常见创建类操作符"><a href="#1-常见创建类操作符" class="headerlink" title="1.常见创建类操作符"></a>1.常见创建类操作符</h3><ul> <li>from:可以把数组、Promise、以及 Iterable 转化为 Observable</li> <li>fromEvent:可以把事件转化为 Observable</li> <li>of:接受一系列的数据,并把它们 emit 出去<ul> <li>如可以使用<code>object$ = Rx.Observable.of({id:1,value:20})</code>转化对象,使用的时候应该是 object.value 或 object.id 的方式</li> </ul> </li> </ul> <h3 id="2-常见转换操作符"><a href="#2-常见转换操作符" class="headerlink" title="2.常见转换操作符"></a>2.常见转换操作符</h3><ul> <li>map<ul> <li>是 mapTo、pluck 操作符的根本</li> <li>举例将上述 pluck 操作符取 target 的 value 值转换成使用 map 操作符</li> </ul> </li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">const width$ = Rx.Observable.fromEvent(width, 'keyup').map(ev=>ev.target.value);</span><br></pre></td></tr></table></figure> <ul> <li>在 angular 中灵活使用 Observable 如下</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 定义一个发起网络请求获取Observable的方法</span><br><span class="line">getQuote():Observable<Quote> {</span><br><span class="line"> const uri = 'http://localhost:3000';</span><br><span class="line"> return this.http.get(uri)</span><br><span class="line"> .map(res => res.json() as Quote);// 将请求返回的内容转换成json并转换成对应定义的Quote对象</span><br></pre></td></tr></table></figure> <ul> <li><p>mapTo</p> <ul> <li><p>比如点击按钮事件或其他事件,我们不需要关注其值内容,只需要知道发生了即可,可以使用 mapTo 定义成一个固定值</p> </li> <li><p>实例代码</p> </li> </ul> </li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">const width$ = Rx.Observable.fromEvent(width, 'keyup').mapTo(1);//此时执行keyup事件,width$的值即为1</span><br><span class="line">//等同于如下</span><br><span class="line">const width$ = Rx.Observable.fromEvent(width, 'keyup').map(_ => 1);//此时执行keyup事件,width$的值即为1</span><br></pre></td></tr></table></figure> <ul> <li>pluck</li> </ul> <h3 id="3-Observable-的性质"><a href="#3-Observable-的性质" class="headerlink" title="3.Observable 的性质"></a>3.Observable 的性质</h3><ul> <li>Observable 有三种状态(即 subscribe 的三个参数):next、error、complete<ul> <li>next 是正常执行时的内容,subscribe 的第一个参数</li> <li>error 是当执行时出错,或监听了 throw 类型 Observable 时的执行内容,subscribe 的第二个参数</li> <li>complete 是 Observable 执行结束时的内容,subscribe 的第三个参数</li> </ul> </li> <li>特殊的 Observable:永不结束、Never、Empty(结束但不发射)、Throw<ul> <li>Never 类型 Observable 表示不会发生也永远不会结束,会在执行过程中导致,也可直接通过 Rx.Observable.error(‘xxx’)的方式声明,结果不会执行 next、error、complete 中任何一个阶段</li> <li>Empty 类型 Observable 不会发射元素会直接结束,会在执行过程中导致,也可以通过 Rx.Observable.empty()的方式声明,结果不会执行 next、error,会执行 complete 阶段</li> <li>Throw 类型 Observable 直接进入 error 状态,会在执行过程中抛出异常导致,也可以直接通过 Rx.Observable.throw(‘xxx’)的方式声明,结果只会执行 error 阶段</li> </ul> </li> </ul> <h2 id="4-常见工作操作符:do"><a href="#4-常见工作操作符:do" class="headerlink" title="4.常见工作操作符:do"></a>4.常见工作操作符:do</h2><ul> <li>do 操作符用于在流处理期间对数据进行操作,实例如下</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">const interval$ = Rx.Observable.interval(100)</span><br><span class="line">.do(v => {</span><br><span class="line"> console.log('val is :'+v);</span><br><span class="line">})</span><br><span class="line">.take(3);</span><br></pre></td></tr></table></figure> <h3 id="5-常见变换类操作符:scan"><a href="#5-常见变换类操作符:scan" class="headerlink" title="5.常见变换类操作符:scan"></a>5.常见变换类操作符:scan</h3><ul> <li>scan(()=>{})接受一个函数,参数 1 是上次处理后的结果,参数 2 是新值内容。实例代码</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">const interval$ = Rx.Observable.interval(100)</span><br><span class="line">.filter(v => val%2 === 0) //表示是偶数才放行</span><br><span class="line">.scan((x,y)=>{return x+y})//表示结果累加操作</span><br><span class="line">.take(4);</span><br><span class="line">//输出结果</span><br><span class="line">// 0[0+0]</span><br><span class="line">// 2[上次结果0+新值2]</span><br><span class="line">// 6[上次结果2+新值4]</span><br><span class="line">// 12[上次结果6+新值6]</span><br><span class="line">//complete执行内容</span><br></pre></td></tr></table></figure> <h3 id="6-常见数学类操作符:reduce"><a href="#6-常见数学类操作符:reduce" class="headerlink" title="6.常见数学类操作符:reduce"></a>6.常见数学类操作符:reduce</h3><ul> <li>reduce 是将流计算结果做统一的最后处理并发射,实例代码</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">const interval$ = Rx.Observable.interval(100)</span><br><span class="line">.filter(v => val%2 === 0) //表示是偶数才放行</span><br><span class="line">.take(4)</span><br><span class="line">.reduce((x,y)=>{return x+y});</span><br><span class="line">//输出结果</span><br><span class="line">// 12[只发射最终值]</span><br></pre></td></tr></table></figure> <h3 id="7-过滤类操作符:filter、take、first-x2F-last、skip…"><a href="#7-过滤类操作符:filter、take、first-x2F-last、skip…" class="headerlink" title="7.过滤类操作符:filter、take、first/last、skip…"></a>7.过滤类操作符:filter、take、first/last、skip…</h3><ul> <li>take(num)表示取流中前 num 个</li> <li>filter(()=>{})表示对流处理的放行判断,如果满足条件则放行,不满足条件则不放行</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">const interval$ = Rx.Observable.interval(100)</span><br><span class="line">.filter(v => val%2 === 0) //表示是偶数才放行</span><br><span class="line">.take(3);//订阅后会输出0 2 4</span><br></pre></td></tr></table></figure> <ul> <li>first()表示取流第一个,相当于 take(1)</li> <li>last()表示取流最后一个</li> <li>skip(num)表示跳过前 num 个</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">const interval$ = Rx.Observable.interval(100)</span><br><span class="line">.filter(v => val%2 === 0) //表示是偶数才放行</span><br><span class="line">.skip(2);//订阅后会输出4 6 8 ...</span><br></pre></td></tr></table></figure> <h3 id="8-常见创建类操作符:Interval、Timer"><a href="#8-常见创建类操作符:Interval、Timer" class="headerlink" title="8.常见创建类操作符:Interval、Timer"></a>8.常见创建类操作符:Interval、Timer</h3><ul> <li>Interval 实例代码</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">const interval$ = Rx.Observable.interval(100).take(3);</span><br><span class="line">interval$.subscribe(</span><br><span class="line"> val => {</span><br><span class="line"> console.log(val) //next状态</span><br><span class="line"> },</span><br><span class="line"> err => {</span><br><span class="line"> console.log(err) // error状态</span><br><span class="line"> },</span><br><span class="line"> ()=> {</span><br><span class="line"> console.log('I am complete') // complete状态</span><br><span class="line"> }</span><br><span class="line">);</span><br><span class="line">//输出结果:interval()是做循环用的,每次发射出来的是索引,故生成的是0/1/2/3/4....,又由于take表示取前多少个,故take(3)表示取前3个</span><br><span class="line">// 0</span><br><span class="line">// 1</span><br><span class="line">// 2</span><br><span class="line">// "I am complete" // complete状态执行的内容</span><br></pre></td></tr></table></figure> <ul> <li>Timer 实例代码</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">const timer$ = Rx.Observable.timer(100,200);//表示100毫秒之后启动,之后以200毫秒的频率一直发送</span><br><span class="line">// 故timer比Interval多出起始延迟时间的设置</span><br><span class="line">timer$.subscribe(v=> console.log(v))</span><br><span class="line">//输出结果</span><br><span class="line">// 0 --运行后100毫秒之后输出</span><br><span class="line">// 1 --输出0后200毫秒之后输出</span><br><span class="line">// 2 --输出1后200毫秒之后输出</span><br><span class="line">// 3 --...</span><br></pre></td></tr></table></figure> <h3 id="9-实例:自行给-rxjs-中-Observable-添加-debug-方法"><a href="#9-实例:自行给-rxjs-中-Observable-添加-debug-方法" class="headerlink" title="9.实例:自行给 rxjs 中 Observable 添加 debug 方法"></a>9.实例:自行给 rxjs 中 Observable 添加 debug 方法</h3><ul> <li>通过 Observable 的原型中定义 debug 方法返回 Observable 对象</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">import {Observable} from 'rxjs/Observable';</span><br><span class="line">import {environment} from '../../environment/environment'</span><br><span class="line">// typescript用于解决自行添加对象属性的报错问题</span><br><span class="line">declare module 'rxjs/Observable' {</span><br><span class="line"> interface Observable<T> {</span><br><span class="line"> debug: (...any) => Observable<T>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">// 给Observable添加debug方法</span><br><span class="line">Observable.prototype.debug = function(message:string) {</span><br><span class="line"> return this.do(</span><br><span class="line"> (next)=>{</span><br><span class="line"> if(!environment.production) {</span><br><span class="line"> console.log(message,next)</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> (err) => {</span><br><span class="line"> if(!environment.production) {</span><br><span class="line"> console.error('ERROR>>',message,err);</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> ()=> {</span><br><span class="line"> if(!environment.production) {</span><br><span class="line"> console.log('Completed -');</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> )</span><br><span class="line">}</span><br><span class="line">//使用时直接在声明Observable期间添加即可以查看对应Observable对象内容,如</span><br><span class="line">this.http.get('url')</span><br><span class="line"> .debug('Test:')</span><br><span class="line"> .map(res => res.json() as Quote);</span><br><span class="line">//在subscribe监听后的会输出对应的log</span><br></pre></td></tr></table></figure> <h3 id="10-过滤类操作符:debounce、debounceTime"><a href="#10-过滤类操作符:debounce、debounceTime" class="headerlink" title="10.过滤类操作符:debounce、debounceTime"></a>10.过滤类操作符:debounce、debounceTime</h3><ul> <li>debounce:比 debounceTime 灵活,debounceTime 只能设定固定的毫秒间隔,而 debounce 可以通过接受的 function 设定毫秒间隔</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">const length$ = Rx.Observable.fromEvent(length,'keyup').pluck('target','value').debounce(()=>Rx.Observable.interval(300));//可以自行修改function返回内容从而决定滤掉的内容</span><br><span class="line">//上述代码含义是:过滤掉300毫秒以内keyup事件监听内容</span><br></pre></td></tr></table></figure> <ul> <li>debouceTime(num):时间滤波器,表示只关注大于等于 num 毫秒间隔的事件内容</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">const length$ = Rx.Observable.fromEvent(length,'keyup').pluck('target','value').debounceTime(300);</span><br><span class="line">//上述代码含义是:过滤掉300毫秒以内keyup事件监听内容</span><br></pre></td></tr></table></figure> <ul> <li>执行图片</li> </ul> <p><img src="https://img-blog.csdnimg.cn/20190602185826234.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODI5NDQ3,size_16,color_FFFFFF,t_70" alt="image"></p> <h3 id="11-过滤类操作符:distinct、distinctUtilChanged"><a href="#11-过滤类操作符:distinct、distinctUtilChanged" class="headerlink" title="11.过滤类操作符:distinct、distinctUtilChanged"></a>11.过滤类操作符:distinct、distinctUtilChanged</h3><ul> <li>distinct:整个序列中,过滤一样的,保留不一样的(要求序列中没有重复的元素)</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">const length$ = Rx.Observable.fromEvent(length,'keyup').pluck('target','value').distinct();//过滤掉整个流中重复的元素</span><br></pre></td></tr></table></figure> <ul> <li>distinctUtilChanged:只跟前一个元素比,过滤一样的,保留不一样的</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">const length$ = Rx.Observable.fromEvent(length,'keyup').pluck('target','value').distinctUtilChanged();//过滤掉流中前一个重复的元素</span><br></pre></td></tr></table></figure> <ul> <li>执行图片</li> </ul> <p><img src="https://img-blog.csdnimg.cn/20190602185839813.png" alt="image"></p> <h3 id="12-合并类操作符:merge、concat、startWith"><a href="#12-合并类操作符:merge、concat、startWith" class="headerlink" title="12.合并类操作符:merge、concat、startWith"></a>12.合并类操作符:merge、concat、startWith</h3><ul> <li>merge:在整个序列中按照流运行状态进行合并</li> <li>concat:在整个序列中将流前后拼接(如拼接的第一个流是无尽流,则永远只会输出第一个流内容,因为第二个流永远不会发生,第一个流没有执行完)</li> <li>startWith:设定流发射的初始值</li> <li>执行图片<br><img src="https://img-blog.csdnimg.cn/20190602185848388.png" alt="image"></li> </ul> <h3 id="13-合并类操作符:combineLatest、withLatestFrom、zip"><a href="#13-合并类操作符:combineLatest、withLatestFrom、zip" class="headerlink" title="13.合并类操作符:combineLatest、withLatestFrom、zip"></a>13.合并类操作符:combineLatest、withLatestFrom、zip</h3><p><img src="https://img-blog.csdnimg.cn/20190602185857966.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODI5NDQ3,size_16,color_FFFFFF,t_70" alt="image"></p> <ul> <li>通过 combineLatest 可以对两个流进行对应的处理操作,实例请参照开头计算面积</li> <li>zip 有对齐的感觉,将两个流对应位置的元素进行处理操作,慢的流决定最终 zip 生成流的速度</li> <li>withLatestFrom 当基准流改变时才会进行流处理,使用方式是基准流.withLatestFrom(其他流),如下</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">const merged$ = length$.withLatestFrom(width$);</span><br></pre></td></tr></table></figure> <ul> <li>区别:zip 有对齐的特性,withLatestFrom 是以源事件流为基准,combineLatest 是无论任何一个流发生改变时都会处理</li> </ul> </div> <footer class="post-footer"> <div class="post-eof"></div> </footer> </article> <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN"> <link itemprop="mainEntityOfPage" href="http://example.com/2022/12/16/VUE-iview-%E6%90%AD%E5%BB%BA%E9%A1%B9%E7%9B%AE-%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/"> <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person"> <meta itemprop="image" content="/images/portal.jpg"> <meta itemprop="name" content="Bruce Chen"> <meta itemprop="description" content="It's better to burn out than to fade away."> </span> <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"> <meta itemprop="name" content="Bruce Chen's Blog"> </span> <header class="post-header"> <h2 class="post-title" itemprop="name headline"> <a href="/2022/12/16/VUE-iview-%E6%90%AD%E5%BB%BA%E9%A1%B9%E7%9B%AE-%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/" class="post-title-link" itemprop="url">VUE iview 搭建项目 - 使用总结</a> </h2> <div class="post-meta"> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-calendar"></i> </span> <span class="post-meta-item-text">发表于</span> <time title="创建时间:2022-12-16 14:45:37 / 修改时间:14:52:32" itemprop="dateCreated datePublished" datetime="2022-12-16T14:45:37+08:00">2022-12-16</time> </span> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-folder"></i> </span> <span class="post-meta-item-text">分类于</span> <span itemprop="about" itemscope itemtype="http://schema.org/Thing"> <a href="/categories/%E5%89%8D%E7%AB%AFUI%E6%A1%86%E6%9E%B6/" itemprop="url" rel="index"><span itemprop="name">前端UI框架</span></a> </span> </span> </div> </header> <div class="post-body" itemprop="articleBody"> <h2 id="iView-放大招了-一口气发布-3-款新产品"><a href="#iView-放大招了-一口气发布-3-款新产品" class="headerlink" title="iView 放大招了!一口气发布 3 款新产品"></a>iView 放大招了!一口气发布 3 款新产品</h2><p>本次发布的主要内容有:</p> <ul> <li>iView Pro</li> <li>iView Pro Admin</li> <li>iView 4.0 的预告</li> <li>iView weapp + mpvue</li> </ul> <p><a target="_blank" rel="noopener" href="https://v.qq.com/x/page/t0905b3h0qj.html?start=4574">iView 2019 发布会视频观看</a></p> <p>使用方法:</p> <p>先安装或引入</p> <h2 id="CDN-引入"><a href="#CDN-引入" class="headerlink" title="CDN 引入"></a>CDN 引入</h2><p>通过 <a target="_blank" rel="noopener" href="https://unpkg.com/iview/">unpkg.com/iview</a> 可以看到 iView 最新版本的资源,也可以切换版本选择需要的资源,在页面上引入 js 和 css 文件即可开始使用:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><!-- import Vue.js --></span><br><span class="line"><script src="//vuejs.org/js/vue.min.js"></script></span><br><span class="line"><!-- import stylesheet --></span><br><span class="line"><link rel="stylesheet" href="//unpkg.com/iview/dist/styles/iview.css"></span><br><span class="line"><!-- import iView --></span><br><span class="line"><script src="//unpkg.com/iview/dist/iview.min.js"></script></span><br></pre></td></tr></table></figure> <h2 id="NPM-安装"><a href="#NPM-安装" class="headerlink" title="NPM 安装"></a>NPM 安装</h2><p>推荐使用 npm 来安装,享受生态圈和工具带来的便利,更好地和 webpack 配合使用,推荐使用 ES2015。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install iview --save</span><br></pre></td></tr></table></figure> <h2 id="使用-vue-CLI3"><a href="#使用-vue-CLI3" class="headerlink" title="使用 vue CLI3"></a>使用 vue CLI3</h2><h3 id="引入-iView-方法一:"><a href="#引入-iView-方法一:" class="headerlink" title="引入 iView 方法一:"></a>引入 iView 方法一:</h3><p>一般在 webpack 入口页面 main.js 中如下配置:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">import Vue from 'vue';</span><br><span class="line">import VueRouter from 'vue-router';</span><br><span class="line">import App from 'components/app.vue';</span><br><span class="line">import Routers from './router.js';</span><br><span class="line">import iView from 'iview';</span><br><span class="line">import 'iview/dist/styles/iview.css';</span><br><span class="line"></span><br><span class="line">Vue.use(VueRouter);</span><br><span class="line">Vue.use(iView);</span><br></pre></td></tr></table></figure> <p>引入成功,在你需要的地方直接使用组件。</p> <h3 id="引入-iView-方法二:"><a href="#引入-iView-方法二:" class="headerlink" title="引入 iView 方法二:"></a>引入 iView 方法二:</h3><h4 id="按需引用"><a href="#按需引用" class="headerlink" title="按需引用"></a>按需引用</h4><p>借助插件<a target="_blank" rel="noopener" href="https://github.com/ant-design/babel-plugin-import"> babel-plugin-import</a>可以实现按需加载组件,减少文件体积。首先安装,并在文件 .babelrc 中配置:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">npm install babel-plugin-import --save-dev</span><br><span class="line"></span><br><span class="line">// .babelrc</span><br><span class="line">{</span><br><span class="line"> "plugins": [["import", {</span><br><span class="line"> "libraryName": "iview",</span><br><span class="line"> "libraryDirectory": "src/components"</span><br><span class="line"> }]]</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>然后这样按需引入组件,就可以减小体积了:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">import { Button, Table } from 'iview';</span><br><span class="line">Vue.component('Button', Button);</span><br><span class="line">Vue.component('Table', Table);</span><br></pre></td></tr></table></figure> <h2 id="特别提醒"><a href="#特别提醒" class="headerlink" title="特别提醒"></a>特别提醒</h2><ul> <li>按需引用仍然需要导入样式,即在 main.js 或根组件执行 import ‘iview/dist/styles/iview.css’;</li> </ul> <p>引入成功之后</p> <p><a target="_blank" rel="noopener" href="https://www.iviewui.com/docs/guide/install">https://www.iviewui.com/docs/guide/install</a></p> <p>打开官方文档就可以随意使用了。</p> </div> <footer class="post-footer"> <div class="post-eof"></div> </footer> </article> <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN"> <link itemprop="mainEntityOfPage" href="http://example.com/2022/12/16/Ant-Design-%E6%A1%86%E6%9E%B6%E6%80%BB%E7%BB%93/"> <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person"> <meta itemprop="image" content="/images/portal.jpg"> <meta itemprop="name" content="Bruce Chen"> <meta itemprop="description" content="It's better to burn out than to fade away."> </span> <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"> <meta itemprop="name" content="Bruce Chen's Blog"> </span> <header class="post-header"> <h2 class="post-title" itemprop="name headline"> <a href="/2022/12/16/Ant-Design-%E6%A1%86%E6%9E%B6%E6%80%BB%E7%BB%93/" class="post-title-link" itemprop="url">Ant Design 框架总结</a> </h2> <div class="post-meta"> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-calendar"></i> </span> <span class="post-meta-item-text">发表于</span> <time title="创建时间:2022-12-16 14:45:30 / 修改时间:14:48:25" itemprop="dateCreated datePublished" datetime="2022-12-16T14:45:30+08:00">2022-12-16</time> </span> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-folder"></i> </span> <span class="post-meta-item-text">分类于</span> <span itemprop="about" itemscope itemtype="http://schema.org/Thing"> <a href="/categories/%E5%89%8D%E7%AB%AFUI%E6%A1%86%E6%9E%B6/" itemprop="url" rel="index"><span itemprop="name">前端UI框架</span></a> </span> </span> </div> </header> <div class="post-body" itemprop="articleBody"> <h1 id="Ant-Design-常用命令汇总"><a href="#Ant-Design-常用命令汇总" class="headerlink" title="Ant Design 常用命令汇总"></a>Ant Design 常用命令汇总</h1><h2 id="一、安装"><a href="#一、安装" class="headerlink" title="一、安装"></a>一、安装</h2><h3 id="1-安装脚手架工具"><a href="#1-安装脚手架工具" class="headerlink" title="1. 安装脚手架工具"></a>1. 安装脚手架工具</h3><p><a target="_blank" rel="noopener" href="https://github.com/ant-design/antd-init/">antd-init</a> 是一个用于演示 antd 如何使用的脚手架工具,真实项目建议使用 <a target="_blank" rel="noopener" href="https://github.com/dvajs/dva-cli">dva-cli</a>。</p> <p>更多功能请参考 <a target="_blank" rel="noopener" href="https://github.com/ant-design/antd-init/">脚手架工具</a> 和 <a target="_blank" rel="noopener" href="http://ant-tool.github.io/">开发工具文档</a>。</p> <p>除了官方提供的脚手架,您也可以使用社区提供的脚手架和范例:</p> <ul> <li><a target="_blank" rel="noopener" href="https://github.com/zuiidea/antd-admin">antd-admin</a></li> <li><a target="_blank" rel="noopener" href="https://github.com/JasonBai007/reactSPA">reactSPA</a></li> <li><a target="_blank" rel="noopener" href="https://github.com/Justin-lu/react-redux-antd">react-redux-antd by Justin-lu</a></li> <li><a target="_blank" rel="noopener" href="https://github.com/okoala/react-redux-antd">react-redux-antd by okoala</a></li> <li><a target="_blank" rel="noopener" href="https://github.com/fireyy/react-antd-admin">react-antd-admin</a></li> <li><a target="_blank" rel="noopener" href="https://github.com/yuzhouisme/react-antd-redux-router-starter">react-antd-redux-router-starter</a></li> <li><a target="_blank" rel="noopener" href="https://github.com/BetaRabbit/react-redux-antd-starter">react-redux-antd-starter</a></li> </ul> <p>更多脚手架可以查看 <a target="_blank" rel="noopener" href="http://scaffold.ant.design/">脚手架市场</a></p> <h3 id="创建一个项目"><a href="#创建一个项目" class="headerlink" title="创建一个项目"></a>创建一个项目</h3><p>使用命令行进行初始化。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ mkdir antd-demo && cd antd-demo</span><br><span class="line">$ antd-init</span><br><span class="line">antd-init 会自动安装 npm 依赖,若有问题则可自行安装。</span><br><span class="line"></span><br><span class="line">若安装缓慢报错,可尝试用 cnpm 或别的镜像源自行安装:rm -rf node_modules && cnpm install。</span><br></pre></td></tr></table></figure> <h3 id="3-使用组件"><a href="#3-使用组件" class="headerlink" title="3. 使用组件"></a>3. 使用组件</h3><p>脚手架会生成一个 Todo 应用实例(一个很有参考价值的 React 上手示例),先不管它,我们用来测试组件。</p> <p>直接用下面的代码替换 index.js 的内容,用 React 的方式直接使用 antd 组件。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">import React from 'react';</span><br><span class="line">import ReactDOM from 'react-dom';</span><br><span class="line">import { LocaleProvider, DatePicker, message } from 'antd';</span><br><span class="line">// 由于 antd 组件的默认文案是英文,所以需要修改为中文</span><br><span class="line">import zhCN from 'antd/lib/locale-provider/zh_CN';</span><br><span class="line">import moment from 'moment';</span><br><span class="line">import 'moment/locale/zh-cn';</span><br><span class="line"></span><br><span class="line">moment.locale('zh-cn');</span><br><span class="line"></span><br><span class="line">class App extends React.Component {</span><br><span class="line"> constructor(props) {</span><br><span class="line"> super(props);</span><br><span class="line"> this.state = {</span><br><span class="line"> date: '',</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> handleChange(date) {</span><br><span class="line"> message.info('您选择的日期是: ' + date.toString());</span><br><span class="line"> this.setState({ date });</span><br><span class="line"> }</span><br><span class="line"> render() {</span><br><span class="line"> return (</span><br><span class="line"> <LocaleProvider locale={zhCN}></span><br><span class="line"> <div style={{ width: 400, margin: '100px auto' }}></span><br><span class="line"> <DatePicker onChange={value => this.handleChange(value)} /></span><br><span class="line"> <div style={{ marginTop: 20 }}>当前日期:{this.state.date.toString()}</div></span><br><span class="line"> </div></span><br><span class="line"> </LocaleProvider></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">ReactDOM.render(<App />, document.getElementById('root'));</span><br></pre></td></tr></table></figure> <h3 id="4-开发调试"><a href="#4-开发调试" class="headerlink" title="4. 开发调试"></a>4. 开发调试</h3><p>一键启动调试,访问 <a target="_blank" rel="noopener" href="http://127.0.0.1:8000/">http://127.0.0.1:8000</a> 查看效果。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm start</span><br></pre></td></tr></table></figure> <h3 id="5-构建和部署"><a href="#5-构建和部署" class="headerlink" title="5. 构建和部署"></a>5. 构建和部署</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm run build</span><br></pre></td></tr></table></figure> <p>入口文件会构建到 dist 目录中,你可以自由部署到不同环境中进行引用。</p> <h2 id="二、Ant-Design-Pro-安装"><a href="#二、Ant-Design-Pro-安装" class="headerlink" title="二、Ant Design Pro 安装"></a>二、Ant Design Pro 安装</h2><p>有两种方式进行安装:</p> <h3 id="1-直接-clone-git-仓库"><a href="#1-直接-clone-git-仓库" class="headerlink" title="1. 直接 clone git 仓库"></a>1. 直接 clone git 仓库</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ git clone --depth=1 https://github.com/ant-design/ant-design-pro.git my-project</span><br><span class="line">$ cd my-project</span><br></pre></td></tr></table></figure> <h3 id="2-使用命令行工具"><a href="#2-使用命令行工具" class="headerlink" title="2. 使用命令行工具"></a>2. 使用命令行工具</h3><p>你可以使用集成化的命令行工具 <a target="_blank" rel="noopener" href="https://github.com/ant-design/ant-design-pro-cli">ant-design-pro-cli</a>。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ npm install ant-design-pro-cli -g</span><br><span class="line">$ mkdir my-project && cd my-project</span><br><span class="line">$ pro new # 安装脚手架</span><br></pre></td></tr></table></figure> <h2 id="三、目录结构"><a href="#三、目录结构" class="headerlink" title="三、目录结构"></a>三、目录结构</h2><p>我们已经为你生成了一个完整的开发框架,提供了涵盖中后台开发的各类功能和坑位,下面是整个项目的目录结构。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">├── mock # 本地模拟数据</span><br><span class="line">├── public</span><br><span class="line">│ └── favicon.ico # Favicon</span><br><span class="line">├── src</span><br><span class="line">│ ├── assets # 本地静态资源</span><br><span class="line">│ ├── common # 应用公用配置,如导航信息</span><br><span class="line">│ ├── components # 业务通用组件</span><br><span class="line">│ ├── e2e # 集成测试用例</span><br><span class="line">│ ├── layouts # 通用布局</span><br><span class="line">│ ├── models # dva model</span><br><span class="line">│ ├── routes # 业务页面入口和常用模板</span><br><span class="line">│ ├── services # 后台接口服务</span><br><span class="line">│ ├── utils # 工具库</span><br><span class="line">│ ├── g2.js # 可视化图形配置</span><br><span class="line">│ ├── theme.js # 主题配置</span><br><span class="line">│ ├── index.ejs # HTML 入口模板</span><br><span class="line">│ ├── index.js # 应用入口</span><br><span class="line">│ ├── index.less # 全局样式</span><br><span class="line">│ └── router.js # 路由入口</span><br><span class="line">├── tests # 测试工具</span><br><span class="line">├── README.md</span><br><span class="line">└── package.json</span><br></pre></td></tr></table></figure> <h2 id="四、本地开发"><a href="#四、本地开发" class="headerlink" title="四、本地开发"></a>四、本地开发</h2><p>安装依赖。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install</span><br></pre></td></tr></table></figure> <p>如果网络状况不佳,可以使用 cnpm 进行加速。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm start</span><br></pre></td></tr></table></figure> <p>启动完成后会自动打开浏览器访问 <a href="http://localhost:8000,你看到下面的页面就代表成功了。">http://localhost:8000,你看到下面的页面就代表成功了。</a></p> <h3 id="1-创建一个项目"><a href="#1-创建一个项目" class="headerlink" title="1. 创建一个项目"></a>1. 创建一个项目</h3><p>可以是已有项目,或者是使用 <a target="_blank" rel="noopener" href="https://github.com/dvajs/dva">dva</a> / create-react(-native)-app 新创建的空项目,你也可以从 <a target="_blank" rel="noopener" href="https://github.com/ant-design/antd-mobile-samples/tree/master/rn-web">官方示例</a> 脚手架里拷贝并修改</p> <blockquote> <p>参考更多<a target="_blank" rel="noopener" href="https://github.com/ant-design/antd-mobile-samples">官方示例集</a> 或者你可以利用 React 生态圈中的 <a target="_blank" rel="noopener" href="https://github.com/enaqx/awesome-react#boilerplates">各种脚手架</a></p> </blockquote> <h3 id="2-安装"><a href="#2-安装" class="headerlink" title="2. 安装"></a>2. 安装</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install antd-mobile --save</span><br></pre></td></tr></table></figure> <h3 id="3-使用"><a href="#3-使用" class="headerlink" title="3. 使用"></a>3. 使用</h3><p>Web 使用方式<br>React Native 使用方式<br>入口页面 (html 或 模板) 相关设置:</p> <blockquote> <p>引入 <a target="_blank" rel="noopener" href="https://github.com/ftlabs/fastclick">FastClick</a> 并且设置 html meta</p> </blockquote> <p>引入 Promise 的 fallback 支持 (部分安卓手机不支持 Promise)</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><!DOCTYPE html></span><br><span class="line"><html></span><br><span class="line"><head></span><br><span class="line"> <!-- set `maximum-scale` for some compatibility issues --></span><br><span class="line"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /></span><br><span class="line"> <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script></span><br><span class="line"> <script></span><br><span class="line"> if ('addEventListener' in document) {</span><br><span class="line"> document.addEventListener('DOMContentLoaded', function() {</span><br><span class="line"> FastClick.attach(document.body);</span><br><span class="line"> }, false);</span><br><span class="line"> }</span><br><span class="line"> if(!window.Promise) {</span><br><span class="line"> document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');</span><br><span class="line"> }</span><br><span class="line"> </script></span><br><span class="line"></head></span><br><span class="line"><body></body></span><br><span class="line"></html></span><br></pre></td></tr></table></figure> <p>组件使用实例:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">import { Button } from 'antd-mobile';</span><br><span class="line">ReactDOM.render(<Button>Start</Button>, mountNode);</span><br></pre></td></tr></table></figure> <p>引入样式:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">import 'antd-mobile/dist/antd-mobile.css'; // or 'antd-mobile/dist/antd-mobile.less'</span><br></pre></td></tr></table></figure> <h3 id="4-按需加载"><a href="#4-按需加载" class="headerlink" title="4. 按需加载"></a>4. 按需加载</h3><p>注意:强烈推荐使用。</p> <p>下面两种方式都可以只加载用到的组件,选择其中一种方式即可。</p> <ul> <li>使用 <a target="_blank" rel="noopener" href="https://github.com/ant-design/babel-plugin-import">babel-plugin-import</a>(推荐)。</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// .babelrc or babel-loader option</span><br><span class="line">{</span><br><span class="line"> "plugins": [</span><br><span class="line"> ["import", { libraryName: "antd-mobile", style: "css" }] // `style: true` 会加载 less 文件</span><br><span class="line"> ]</span><br><span class="line">}</span><br><span class="line">然后只需从 antd-mobile 引入模块即可,无需单独引入样式。</span><br><span class="line"></span><br><span class="line">// babel-plugin-import 会帮助你加载 JS 和 CSS</span><br><span class="line">import { DatePicker } from 'antd-mobile';</span><br></pre></td></tr></table></figure> <ul> <li>手动引入</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">import DatePicker from 'antd-mobile/lib/date-picker'; // 加载 JS</span><br><span class="line">import 'antd-mobile/lib/date-picker/style/css'; // 加载 CSS</span><br><span class="line">// import 'antd-mobile/lib/date-picker/style'; // 加载 LESS</span><br></pre></td></tr></table></figure> <p><strong>更多增强 (非必须):</strong></p> <blockquote> <p>如何自定义主题?<a target="_blank" rel="noopener" href="https://github.com/ant-design/ant-design-mobile/blob/master/docs/react/theme-config.zh-CN.md">见此文档</a>, 基于 antd-mobile 的自定义 UI 库:<a target="_blank" rel="noopener" href="https://github.com/ant-design/antd-mobile-samples/tree/master/web-custom-ui">web-custom-ui</a> / <a target="_blank" rel="noopener" href="https://github.com/ant-design/antd-mobile-samples/tree/master/web-custom-ui-pro">web-custom-ui-pro</a></p> </blockquote> <h1 id="antd-常用组件的问题及实现"><a href="#antd-常用组件的问题及实现" class="headerlink" title="antd 常用组件的问题及实现"></a>antd 常用组件的问题及实现</h1><p>在基于 react 的 web 后台开发中,常常会使用到 antd 组件,可以使用现有所提供的,也可以自己再次封装使用。基于平时开发中遇到较多关于 antd 组件问题的情况,记录一下平时常用的代码实现。</p> <h2 id="1-Radio"><a href="#1-Radio" class="headerlink" title="1.Radio"></a>1.Radio</h2><p>单选框</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><FormItem {...formItemLayout} label="是否启用" required></span><br><span class="line"> {getFieldDecorator('enabled',{</span><br><span class="line"> initialValue: data.enabled,</span><br><span class="line"> rules: [{ required: true, message: '请选择是否启用' }],</span><br><span class="line"> })(</span><br><span class="line"> <RadioGroup></span><br><span class="line"> <Radio value={true}>启用</Radio></span><br><span class="line"> <Radio value={false}>禁用</Radio></span><br><span class="line"> </RadioGroup></span><br><span class="line"> )}</span><br><span class="line"></FormItem></span><br></pre></td></tr></table></figure> <h2 id="2-Select"><a href="#2-Select" class="headerlink" title="2.Select"></a>2.Select</h2><p>下拉选择,数据源根据接口获取</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><FormItem {...formItemLayout} label='xxx'></span><br><span class="line"> {getFieldDecorator('feedHabit', {</span><br><span class="line"> initialValue: (detail && detail.feedHabit) || undefined,</span><br><span class="line"> rules: [</span><br><span class="line"> { required: false, message: 'xxx' }</span><br><span class="line"> ]</span><br><span class="line"> })(</span><br><span class="line"> <Select</span><br><span class="line"> showSearch</span><br><span class="line"> allowClear</span><br><span class="line"> placeholder='xxx'</span><br><span class="line"> filterOption={(inputValue, option) => option.props.children.indexOf(inputValue) > -1}</span><br><span class="line"> notFoundContent={</span><br><span class="line"> <Empty</span><br><span class="line"> image={Empty.PRESENTED_IMAGE_SIMPLE}</span><br><span class="line"> style={{ margin: 8 }}</span><br><span class="line"> ></span><br><span class="line"> <Button</span><br><span class="line"> type='primary'</span><br><span class="line"> onClick={() => dispatch({ type: `${namespace}/getFeedHabitList` })}</span><br><span class="line"> >刷新</span><br><span class="line"> </Button></span><br><span class="line"> </Empty></span><br><span class="line"> }</span><br><span class="line"> ></span><br><span class="line"> {</span><br><span class="line"> feedHabitList && feedHabitList.map((item, index) => (</span><br><span class="line"> <Select.Option key={item.code} value={item.code}>{item.name}</Select.Option></span><br><span class="line"> ))</span><br><span class="line"> }</span><br><span class="line"> </Select></span><br><span class="line"> )}</span><br><span class="line"> </FormItem></span><br></pre></td></tr></table></figure> <h2 id="3-Switch"><a href="#3-Switch" class="headerlink" title="3.Switch"></a>3.Switch</h2><p>开发的切换</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><Form.Item {...formItemLayout} label='xxx'></span><br><span class="line"> {getFieldDecorator('enabledAutoFeed', {</span><br><span class="line"> initialValue: (detail && detail.enabledAutoFeed) || undefined,</span><br><span class="line"> valuePropName: 'checked', // 记得加上这个属性</span><br><span class="line"> rules: [</span><br><span class="line"> {required: false, message: 'xxx'}</span><br><span class="line"> ]</span><br><span class="line"> })(</span><br><span class="line"> <Switch</span><br><span class="line"> checkedChildren='是'</span><br><span class="line"> unCheckedChildren='否'</span><br><span class="line"> onChange={(val, event) => {</span><br><span class="line"> event.preventDefault();</span><br><span class="line"> event.stopPropagation();</span><br><span class="line"> this.setState({ enabledAutoFeed: val }) // 将当前改变的值存至state中,提交时直接用state中存储的值就好了</span><br><span class="line"> }}</span><br><span class="line"> /></span><br><span class="line"> )}</span><br><span class="line"></Form.Item></span><br></pre></td></tr></table></figure> <h2 id="4-Cascader"><a href="#4-Cascader" class="headerlink" title="4.Cascader"></a>4.Cascader</h2><h3 id="情况一:只有两个层级数"><a href="#情况一:只有两个层级数" class="headerlink" title="情况一:只有两个层级数"></a>情况一:只有两个层级数</h3><p>此情况组件用例是在一个新增/编辑的弹出框中使用,新增时不带数据,可选择;编辑时带出默认数据,也可选择;层级是 2 级。<br>开发思路:</p> <ul> <li>(1)新增时,不带默认数据出来,但是已经请求到第一层级的数据,点击第一层级中的某一项 item 时,加载请求第二层级的数据给到 item 的 children 中,这样就可以显示第二层级的数据了;</li> <li>(2)编辑时,带出默认数据,此时 defaultMaterialIds 应该是有两个 id 值的,如:[parentId, sonId],但是在点编辑弹出弹框而还没有点击级联组件时,只显示了第一层级的数据,因为此时页面起始已经有第一层级的数据源,而不知道第二层级的数据源,只单纯的有第二层级项的 id(也就是 sonId),是不知道它的 label(或 name)的,思路是:在点击编辑的时候,拿到它的第一层级 id(也就是 parentId),请求第二层级的数据就好了(其实就是点击编辑时再次调用 loadData 方法~~~)</li> </ul> <p>附上实现逻辑代码:</p> <p>1.弹框中(editModal.jsx):</p> <p>render 中:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><Form.Item {...formItemLayout} label='物料名称'></span><br><span class="line"> {getFieldDecorator(`materialId`, {</span><br><span class="line"> // defaultMaterialIds 是默认的数据(一个数组形式),如:[parentId, sonId]</span><br><span class="line"> initialValue: defaultMaterialIds || []</span><br><span class="line"> })(</span><br><span class="line"> <Cascader</span><br><span class="line"> fieldNames={fieldNames}</span><br><span class="line"> options={materialLabel}</span><br><span class="line"> loadData={this.loadData}</span><br><span class="line"> onChange={this.onChange}</span><br><span class="line"> placeholder='请选择物料名称'</span><br><span class="line"> changeOnSelect</span><br><span class="line"> /></span><br><span class="line"> )}</span><br><span class="line"> </Form.Item></span><br></pre></td></tr></table></figure> <p>相应方法:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">// 级联-选择后的回调</span><br><span class="line"> onChange = (value, selectedOptions) => {</span><br><span class="line"> const { dispatch, namespace } = this.props;</span><br><span class="line"> const name = [];</span><br><span class="line"> const id = [];</span><br><span class="line"> const code = [];</span><br><span class="line"> // console.log(value, selectedOptions);</span><br><span class="line"></span><br><span class="line"> selectedOptions.map((v) => {</span><br><span class="line"> name.push(v.name);</span><br><span class="line"> id.push(v.id);</span><br><span class="line"> code.push(v.code)</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> this.setState({</span><br><span class="line"> currentId: value</span><br><span class="line"> })</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> // 动态加载选项</span><br><span class="line"> loadData = (selectedOptions) => {</span><br><span class="line"> const { dispatch, namespace, materialLabel, form: { setFieldsValue }, } = this.props;</span><br><span class="line"> const targetOption = selectedOptions[selectedOptions.length - 1];</span><br><span class="line"> targetOption.loading = true;</span><br><span class="line"> // 异步加载数据,targetOption 是选中的那一项数据</span><br><span class="line"> dispatch({</span><br><span class="line"> type:`${namespace}/getMaterialList`,</span><br><span class="line"> payload:{ targetOption },</span><br><span class="line"> callback:(res)=>{</span><br><span class="line"> targetOption.loading = false;</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> }</span><br></pre></td></tr></table></figure> <p>2.view.jsx 中(editModal 在 view 中被引用):</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">// 点击编辑时,传第一级id,请求第二级数据显示</span><br><span class="line">loadData = (parentId) => {</span><br><span class="line">const { dispatch, pigfarmConfListIndexMod: { materialLabel } }= this.props;</span><br><span class="line">// materialLabel为第一层级数据源</span><br><span class="line">const targetOption = materialLabel.find(item => item.id === parentId)</span><br><span class="line"></span><br><span class="line">// 异步加载数据</span><br><span class="line">dispatch({</span><br><span class="line"> type:`${namespace}/getMaterialList`,</span><br><span class="line"> payload:{ targetOption },</span><br><span class="line">})</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 点击编辑时调用的方法</span><br><span class="line">showModal = ({data = null, type = 'add' }) => {</span><br><span class="line"> const { dispatch } = this.props;</span><br><span class="line"> if(type === 'add') {</span><br><span class="line"> dispatch({</span><br><span class="line"> type: `${namespace}/updateStore`,</span><br><span class="line"> payload: { showModal: true, detail: data, modalType: type, }</span><br><span class="line"> });</span><br><span class="line"> }else {</span><br><span class="line"> // 编辑时拿到parentId请求第二层级数据</span><br><span class="line"> this.loadData(data.parentId);</span><br><span class="line"> dispatch({</span><br><span class="line"> type: `${namespace}/updateStore`,</span><br><span class="line"> payload: { showModal: true, detail: data, defaultMaterialIds: [data.parentId, data.materialId], modalType: type, }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure> <p>3.mod.js 中:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">// 获取物料列表---这是第二层级</span><br><span class="line">*getMaterialList({ payload, callback }, { put, call, select }) {</span><br><span class="line"> // ...</span><br><span class="line"> // targetOption 是点击的第一层级的项</span><br><span class="line"> let targetOption = payload.targetOption;</span><br><span class="line"> const children = [];</span><br><span class="line"> // 第一层级数据源materialLabel是一个对象数组,数组中的每一项都是一个对象,带有id,name等属性</span><br><span class="line"> const _materialLabel = [...materialLabel]</span><br><span class="line"> // payload.targetOption.id是点击的第一层级的项,用来获取第二层级的数据源</span><br><span class="line"> const result = yield call(urlMaterialList, {id: payload.targetOption.id});</span><br><span class="line"> // idx是在第一层级中点击的项的位置</span><br><span class="line"> const idx = _materialLabel.findIndex(item=>item.id===targetOption.id)</span><br><span class="line"> // 深拷贝第一层级中选中的项,后面会加上一个children属性,而children就是第二层级的数据源</span><br><span class="line"> // 把这个第二层级数据源 替换掉 第一层级中选中的那个项</span><br><span class="line"> // 然后赋值给整个第一层级的数据源,因为第一层级选中的那个项已经加上了children属性,可以显示第二层级的数据了</span><br><span class="line"></span><br><span class="line"> const materialItem = cloneDeep(targetOption);</span><br><span class="line"> // ...</span><br><span class="line"> if(result.data.length > 0) {</span><br><span class="line"> result.data.map(item => {</span><br><span class="line"> // 因为第2级就是末级了,所以isLeaf为true</span><br><span class="line"> let obj = { ...item, isLeaf: true };</span><br><span class="line"> children.push(obj);</span><br><span class="line"> })</span><br><span class="line"> // 当前项加上children ,loading 属性</span><br><span class="line"> materialItem.children = children;</span><br><span class="line"> materialItem.loading = false;</span><br><span class="line"> // 然后替换第一层级中选中的那一项</span><br><span class="line"> _materialLabel.splice(idx,1,materialItem)</span><br><span class="line"> yield put({</span><br><span class="line"> type: 'updateStore',</span><br><span class="line"> payload: {</span><br><span class="line"> materialList: result.data,</span><br><span class="line"> materialLabel:_materialLabel</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> callback && callback();</span><br><span class="line"> }</span><br><span class="line"> // ...</span><br><span class="line">},</span><br></pre></td></tr></table></figure> <h3 id="情况二:多个层级(以下例子为-4-级:地址的省-市-区-镇)"><a href="#情况二:多个层级(以下例子为-4-级:地址的省-市-区-镇)" class="headerlink" title="情况二:多个层级(以下例子为 4 级:地址的省-市-区-镇)"></a>情况二:多个层级(以下例子为 4 级:地址的省-市-区-镇)</h3><p>此情况是用于获取一个中国地区各省市级地址信息的获取及展示/显示;<br>在平时开发项目中,一般会封装好一个组件,然后多个地方使用,提高开发效率。</p> <p><strong>开发思路</strong>:一开始获得第一层级的地址 a,将 a 的 id 传至后端请求 a 的第二层级 b,b 的 id 又请求第三层级 c…以此类推,将请求到的数据 b 每一项加上 isLeaf 属性(末级为 true,非末级为 false),然后将这个新的 b 赋值给 a 的 children,就可以显示第二层级 b 的数据了,依此类推,第三第四级也是…</p> <p>以下是有关子组件及父组件的逻辑及实现:(主要看注释吧~)</p> <h4 id="1-子组件中(封装-export-名为-CascaderAddress-):"><a href="#1-子组件中(封装-export-名为-CascaderAddress-):" class="headerlink" title="1.子组件中(封装 export 名为 CascaderAddress ):"></a>1.子组件中(封装 export 名为 CascaderAddress ):</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><Cascader</span><br><span class="line"> // 在实际编辑器中,应该不能这样注释的,这里是为理解外加的注释</span><br><span class="line"> {...options} // 可选项配置,如placeHolder,changeOnSelect等,当然也可直接分开写</span><br><span class="line"> value={defaultAddress} // 指定的或默认的数据</span><br><span class="line"> options={addressOptions} // 级联地址的数据源</span><br><span class="line"> loadData={this.loadAddressData} // 动态加载数据(如点击第一层,加载第二层...)</span><br><span class="line"> onChange={this.onChangeAddress} // 点击/选择完成后的回调</span><br><span class="line"> fieldNames={fieldNames} // 自定义数据源options的label,value,children</span><br><span class="line">/></span><br></pre></td></tr></table></figure> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">// 地址选择</span><br><span class="line"> onChangeAddress = (value, selectedOptions) => {</span><br><span class="line"> const { changeCallback } = this.props;</span><br><span class="line"> const dataString = [];</span><br><span class="line"> const ids = [];</span><br><span class="line"></span><br><span class="line"> selectedOptions.map((v) => {</span><br><span class="line"> dataString.push(v.areaName);</span><br><span class="line"> ids.push(v.id);</span><br><span class="line"> });</span><br><span class="line"> // 选择完成后的回调,将选择地址的id,name,以及其他信息存起来备用</span><br><span class="line"> changeCallback({data: selectedOptions, ids, dataString});</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 获取地址</span><br><span class="line"> loadAddressData = selectedOptions => {</span><br><span class="line"> const { loadData, addressOptions } = this.props;</span><br><span class="line"> const targetOption = selectedOptions[selectedOptions.length - 1];</span><br><span class="line"> targetOption.loading = true;</span><br><span class="line"> // 将当前选择项的id请求后端拿到下一层级数据(加上isLeaf属性),赋给当前选择项的children,就可以显示出下一层级的数据了</span><br><span class="line"> this.getAddressOptions(targetOption.id, (list) => {</span><br><span class="line"> targetOption.loading = false;</span><br><span class="line"> targetOption.children = list;</span><br><span class="line"></span><br><span class="line"> loadData(addressOptions);</span><br><span class="line"> })</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line">// 请求地址数据</span><br><span class="line"> getAddressOptions = (parentId, callback) => {</span><br><span class="line"> const { max } = this.state;</span><br><span class="line"> const { request, url } = this.props;</span><br><span class="line"> const params = {</span><br><span class="line"> 'id': parentId || '0', // '0'表示请求第一层级,看后端需要传什么</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> request({</span><br><span class="line"> // 拿点击的那一项的id请求下一层级的数据(也就是children)</span><br><span class="line"> url: `${url}/${params.id}`,</span><br><span class="line"> method: 'get',</span><br><span class="line"> data: params,</span><br><span class="line"> })</span><br><span class="line"> .then((res) => {</span><br><span class="line"> if(res.code === '000000') {</span><br><span class="line"> let list = []</span><br><span class="line"> if(res.data && res.data.length) {</span><br><span class="line"> // 判断是否为末级,为每一项加上isLeaf属性</span><br><span class="line"> list = res.data.map(item => ({ ...item, isLeaf: item.areaLevel > max }));</span><br><span class="line"> }</span><br><span class="line"> // 回调显示当前list数据</span><br><span class="line"> callback(list);</span><br><span class="line"> } else {</span><br><span class="line"> message.error(res.msg);</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> }</span><br></pre></td></tr></table></figure> <h4 id="2-父组件中引用:"><a href="#2-父组件中引用:" class="headerlink" title="2.父组件中引用:"></a>2.父组件中引用:</h4><p>(1)view.jsx</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// 注意farmAddress为当前级联控件的唯一标识</span><br><span class="line"><Form.Item {...formItemLayout} label='地址'></span><br><span class="line"> {getFieldDecorator('farmAddress', {</span><br><span class="line"> initialValue: defaultAddressIds || undefined,</span><br><span class="line"> rules: [</span><br><span class="line"> { required: true, message: '请输入地址' },</span><br><span class="line"> ],</span><br><span class="line"> })(</span><br><span class="line"> <CascaderAddress {...cascaderProps}/></span><br><span class="line"> )}</span><br><span class="line"></Form.Item></span><br></pre></td></tr></table></figure> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">// 级联数据值名称配置</span><br><span class="line"> const cascaderOptionLabel = {</span><br><span class="line"> label: 'areaName', // 显示的是label,自定义为areaName</span><br><span class="line"> value: 'id', // 传值的是value,自定义为id</span><br><span class="line"> children: 'children'</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 级联地址选择配置项目</span><br><span class="line"> const cascaderProps = {</span><br><span class="line"> request,</span><br><span class="line"> url: `xxx`,</span><br><span class="line"> addressOptions, // 数据源</span><br><span class="line"> defaultAddress: defaultAddressIds, // 默认数据,</span><br><span class="line"> fieldNames: cascaderOptionLabel, // 自定义配置项</span><br><span class="line"> loadData: (data) => { // 动态加载数据</span><br><span class="line"> dispatch({ type: `${namespace}/updateStore`, payload: { addressOptions: [...data] } })</span><br><span class="line"> },</span><br><span class="line"> // 选择之后的回调,farmAddress为表单控件中存储的数据,可直接/处理之后提交~</span><br><span class="line"> changeCallback: ({data, dataString, ids}) => {</span><br><span class="line"> setFieldsValue({ farmAddress: ids }); // 设置form表单数据</span><br><span class="line"> dispatch({ type: `${namespace}/updateStore`, payload: { defaultAddressIds: ids } }); // 存储至mod备用</span><br><span class="line"> },</span><br><span class="line"> options: {</span><br><span class="line"> placeholder: '请选择地址',</span><br><span class="line"> changeOnSelect: true,</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>(2)mod.js</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">// 获取地址列表</span><br><span class="line">*getAddressOptions({ payload = { id: '' }, callback }, { put, call, select }) {</span><br><span class="line"> ...</span><br><span class="line"> // ...假设调用接口请求到数据存至res.data</span><br><span class="line"> let addressOptions = []</span><br><span class="line"> if(res.data && res.data.length) {</span><br><span class="line"> addressOptions = res.data.map(item => ({ id: item.id, value: item.id, areaName: item.areaName, isLeaf: false }))</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> yield put({</span><br><span class="line"> type: 'updateStore',</span><br><span class="line"> payload: {</span><br><span class="line"> addressOptions,</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">},</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> // 根据ID获取该地址信息包含各级数据</span><br><span class="line">* getAreaInfo({ payload, callback }, { call, put, select }) {</span><br><span class="line"> ...</span><br><span class="line"> // ...</span><br><span class="line"> let addressOptions = [];</span><br><span class="line"> if(result.data && result.data.length) {</span><br><span class="line"> addressOptions = result.data[0].list;</span><br><span class="line"></span><br><span class="line"> addressOptions.map( v => { // 省</span><br><span class="line"> v.isLeaf = false;</span><br><span class="line"> if(v.id === result.data[0].selected.id) {</span><br><span class="line"> v.children = result.data[1].list;</span><br><span class="line"></span><br><span class="line"> v.children.map( v1 => { // 市</span><br><span class="line"> v1.isLeaf = false;</span><br><span class="line"> if(v1.id === result.data[1].selected.id) {</span><br><span class="line"> v1.children = result.data[2].list;</span><br><span class="line"></span><br><span class="line"> v1.children.map( v2 => { // 区</span><br><span class="line"> v2.isLeaf = false;</span><br><span class="line"> if(v2.id === result.data[2].selected.id) {</span><br><span class="line"> v2.children = result.data[3].list;</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> yield put({</span><br><span class="line"> type: 'updateStore',</span><br><span class="line"> payload: {</span><br><span class="line"> addressOptions,</span><br><span class="line"> detailAddress: result.data,</span><br><span class="line"> // 后端返回的默认地址id,组成一个数组展示</span><br><span class="line"> defaultAddressIds: [detail.addressProvince, detail.addressCity, detail.addressZone, detail.addressTown]</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> callback && callback();</span><br><span class="line"> // ...</span><br><span class="line">},</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>其实两个情况很类似的,都是将取到的下一级的数据,每一项都加上 isLeaf 属性,然后将这个新的数据加上 children,loading 属性,替换上一级中被选择的那一项,就可以显示下一级的数据了~~~</p> <h3 id="5-Table"><a href="#5-Table" class="headerlink" title="5.Table"></a>5.Table</h3><p>某些列表,后端不做分页,需要前端分页时,table 的一般配置:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><Table</span><br><span class="line"> bordered // 给整个table加上边框</span><br><span class="line"> dataSource={tableList}</span><br><span class="line"> columns={tableColumns}</span><br><span class="line"> rowkey={record => record.id}</span><br><span class="line"> pagination={{</span><br><span class="line"> showQuickJumper: true,</span><br><span class="line"> showSizeChanger: true,</span><br><span class="line"> showTotal: total => `共${total}条`,</span><br><span class="line"> pageSizeOptions:['10','20','50','100'],</span><br><span class="line"> // pageSize: 5, // 固定5条一页,有pageSize就不用pageSizeOptions了</span><br><span class="line"> }}</span><br><span class="line"> /></span><br></pre></td></tr></table></figure> </div> <footer class="post-footer"> <div class="post-eof"></div> </footer> </article> <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN"> <link itemprop="mainEntityOfPage" href="http://example.com/2022/12/16/Taro-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/"> <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person"> <meta itemprop="image" content="/images/portal.jpg"> <meta itemprop="name" content="Bruce Chen"> <meta itemprop="description" content="It's better to burn out than to fade away."> </span> <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"> <meta itemprop="name" content="Bruce Chen's Blog"> </span> <header class="post-header"> <h2 class="post-title" itemprop="name headline"> <a href="/2022/12/16/Taro-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/" class="post-title-link" itemprop="url">Taro 使用指南</a> </h2> <div class="post-meta"> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-calendar"></i> </span> <span class="post-meta-item-text">发表于</span> <time title="创建时间:2022-12-16 14:45:24 / 修改时间:14:52:06" itemprop="dateCreated datePublished" datetime="2022-12-16T14:45:24+08:00">2022-12-16</time> </span> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-folder"></i> </span> <span class="post-meta-item-text">分类于</span> <span itemprop="about" itemscope itemtype="http://schema.org/Thing"> <a href="/categories/%E5%89%8D%E7%AB%AFUI%E6%A1%86%E6%9E%B6/" itemprop="url" rel="index"><span itemprop="name">前端UI框架</span></a> </span> </span> </div> </header> <div class="post-body" itemprop="articleBody"> <p>Taro 就是可以用 React 语法写小程序的框架,拥有多端转换能力,一套代码可编译为微信小程序、百度小程序、支付宝小程序、H5、RN 等</p> <h2 id="1、入门"><a href="#1、入门" class="headerlink" title="1、入门"></a>1、入门</h2><h3 id="1-1、安装-CLI-及项目初始化"><a href="#1-1、安装-CLI-及项目初始化" class="headerlink" title="1.1、安装 CLI 及项目初始化"></a>1.1、安装 CLI 及项目初始化</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm install -g @tarojs/cli</span><br><span class="line">taro init 项目名</span><br></pre></td></tr></table></figure> <p><img src="https://upload-images.jianshu.io/upload_images/6813214-f9a34c9f56809815.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/607/format/webp" alt="image"></p> <p>可以选择使用 SCSS 、TS、Redux</p> <h3 id="1-2、编译至各种平台"><a href="#1-2、编译至各种平台" class="headerlink" title="1.2、编译至各种平台"></a>1.2、编译至各种平台</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// 编译为小程序</span><br><span class="line">npm run dev:weapp</span><br><span class="line">npm run build:weapp</span><br><span class="line">// 编译为 H5</span><br><span class="line">npm run dev:h5</span><br><span class="line">// 编译为 RN</span><br><span class="line">npm run dev:rn</span><br></pre></td></tr></table></figure> <p>编译为小程序时,小程序代码位于 dist 目录下</p> <h3 id="1-3、微信小程序须知"><a href="#1-3、微信小程序须知" class="headerlink" title="1.3、微信小程序须知"></a>1.3、微信小程序须知</h3><ul> <li>小程序注册<br/><br><a target="_blank" rel="noopener" href="https://links.jianshu.com/go?to=https://mp.weixin.qq.com/wxopen/waregister?action=step1">注册地址</a>,注意一个邮箱只能注册一个小程序</li> <li>小程序后台<br/><br><a target="_blank" rel="noopener" href="https://links.jianshu.com/go?to=https://mp.weixin.qq.com/wxopen/authprofile?action=index&token=1030929877&lang=zh_CN">后台地址</a>,后台可查看当前小程序版本,添加开发者,查看小程序 AppID 和 AppSecret 等功能</li> <li>小程序开发者工具<br/><br><a target="_blank" rel="noopener" href="https://links.jianshu.com/go?to=https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html">下载地址</a></li> <li>小程序开发流程<br/><br>1、在开发者工具中新建项目,填入对应的 AppID<br/><br>2、在小程序后台配置服务器域名(开发-服务器域名)</li> <li>小程序发布流程<br/><br>1、在开发者工具中上传代码<br/><br>2、在管理后台-版本管理-开发版本中提交审核,注意提交审核前可先生成体验版,确认体验版没问题后再提交审核</li> </ul> <h2 id="2、注意点"><a href="#2、注意点" class="headerlink" title="2、注意点"></a>2、注意点</h2><p>由于 Taro 编译后的代码已经经过了转义和压缩,因此还需要注意微信开发者工具的项目设置</p> <p><img src="https://upload-images.jianshu.io/upload_images/6813214-5e31c3c6ab19a603.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/710/format/webp" alt="image"></p> <ul> <li>只能在 render 里使用 jsx 语法</li> <li>不能在包含 JSX 元素的 map 循环中使用 if 表达式<br>尽量在 map 循环中使用条件表达式或逻辑表达式</li> <li>不能使用 Array.map 之外的方法操作 JSX 数组<br>先处理好需要遍历的数组,然后再用处理好的数组调用 map 方法。</li> <li>不能在 JSX 参数中使用匿名函数<br>使用 bind 或 类参数绑定函数。</li> <li>不能在 JSX 参数中使用对象展开符<br>开发者自行赋值:</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><View {...props} />  // wrong</span><br><span class="line"><View id={id} title={title} /> // ok</span><br></pre></td></tr></table></figure> <ul> <li>不允许在 JSX 参数(props)中传入 JSX 元素</li> <li>不支持无状态组件(Stateless Component)</li> <li>函数名驼峰,且不能包含数字,不能以下划线开始或结尾以及长度不超过20</li> <li>必须使用单引号,不支持双引号</li> <li>对于 process.env,建议直接书写 process.env.NODE_ENV,而不是解构</li> <li>组件传递函数属性名以 on 开头</li> <li>小程序端不要将在模板中用到的数据设置为 undefined</li> <li>小程序端不要在组件中打印 this.props.children</li> <li>组件属性传递注意<br/><br>不要以 id、class、style 作为自定义组件的属性与内部 state 的名称,因为这些属性名在微信小程序中会丢失。</li> <li>组件 state 与 props 里字段重名的问题<br/><br>不要在 state 与 props 上用同名的字段,因为这些被字段在微信小程序中都会挂在 data 上。</li> <li>小程序中页面生命周期 componentWillMount 不一致问题</li> <li>组件的 constructor 与 render 提前调用</li> </ul> <h2 id="3、Taro-实战"><a href="#3、Taro-实战" class="headerlink" title="3、Taro 实战"></a>3、Taro 实战</h2><h3 id="3-1、相关库介绍"><a href="#3-1、相关库介绍" class="headerlink" title="3.1、相关库介绍"></a>3.1、相关库介绍</h3><ul> <li>@tarojs/taro<br/><br>taro 核心库,相当于 react</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">import Taro, { Component } from '@tarojs/taro'</span><br><span class="line">class App extends Component {}</span><br><span class="line">Taro.render(<App />, document.getElementById('app'))</span><br></pre></td></tr></table></figure> <ul> <li>@tarojs/redux<br>taro 状态管理辅助库,相当于 react-redux</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">import { Provider,connect } from '@tarojs/redux'</span><br></pre></td></tr></table></figure> <ul> <li>@tarojs/components<br>taro 为屏蔽多端差异而制定的标准组件库,在 taro 中不能直接写常规的 HTML 标签,而必须用这个组件库里的标签,就像写 RN 一样:</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">import { View, Button, Text } from "@tarojs/components";</span><br><span class="line"><View className='index'></span><br><span class="line"> <Button className='add_btn' onClick={this.props.add}></span><br><span class="line"> +</span><br><span class="line"> </Button></span><br><span class="line"> <Text> Hello, World </Text></span><br><span class="line"></View></span><br></pre></td></tr></table></figure> <ul> <li>@tarojs/async-await<br>taro 支持 async await 写法库</li> <li>taro-ui<br>taro 为屏蔽多端差异而制定的业务组件库,比如 Tabs,Modal,Menu 之类的常用的业务组件</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">import { AtTabs, AtTabsPane } from "taro-ui";</span><br><span class="line"> <AtTabs</span><br><span class="line"> current={this.state.current}</span><br><span class="line"> tabList={tabList}</span><br><span class="line"> onClick={this.handleClick.bind(this)}</span><br><span class="line"> ></span><br><span class="line"> <AtTabsPane current={this.state.current} index={0}></span><br><span class="line"> <AllContainer /></span><br><span class="line"> </AtTabsPane></span><br><span class="line"> <AtTabsPane current={this.state.current} index={1}></span><br><span class="line"> <View style='padding: 100px 50px;background-color: #FAFBFC;text-align: center;'></span><br><span class="line"> 标签页二的内容</span><br><span class="line"> </View></span><br><span class="line"> </AtTabsPane></span><br><span class="line"> <AtTabsPane current={this.state.current} index={2}></span><br><span class="line"> <View style='padding: 100px 50px;background-color: #FAFBFC;text-align: center;'></span><br><span class="line"> 标签页三的内容</span><br><span class="line"> </View></span><br><span class="line"> </AtTabsPane></span><br><span class="line"> </AtTabs></span><br></pre></td></tr></table></figure> <h3 id="3-2、常用工具类封装"><a href="#3-2、常用工具类封装" class="headerlink" title="3.2、常用工具类封装"></a>3.2、常用工具类封装</h3><ul> <li>本地存储</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">import Taro from "@tarojs/taro";</span><br><span class="line"></span><br><span class="line">class Store {</span><br><span class="line"> removeItem(key) {</span><br><span class="line"> return Taro.removeStorageSync(key);</span><br><span class="line"> }</span><br><span class="line"> getItem(key) {</span><br><span class="line"> return Taro.getStorageSync(key);</span><br><span class="line"> }</span><br><span class="line"> setItem(key, value) {</span><br><span class="line"> return Taro.setStorageSync(key, value);</span><br><span class="line"> }</span><br><span class="line"> clear() {</span><br><span class="line"> return Taro.clearStorageSync();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default new Store();</span><br></pre></td></tr></table></figure> <ul> <li>请求封装</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line">import Taro from '@tarojs/taro'</span><br><span class="line">import {</span><br><span class="line"> API_USER_LOGIN</span><br><span class="line">} from '@constants/api'</span><br><span class="line"></span><br><span class="line">const CODE_SUCCESS = '200'</span><br><span class="line">const CODE_AUTH_EXPIRED = '600'</span><br><span class="line"></span><br><span class="line">function getStorage(key) {</span><br><span class="line"> return Taro.getStorage({</span><br><span class="line"> key</span><br><span class="line"> }).then(res => res.data).catch(() => '')</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function updateStorage(data = {}) {</span><br><span class="line"> return Promise.all([</span><br><span class="line"> Taro.setStorage({</span><br><span class="line"> key: 'token',</span><br><span class="line"> data: data['3rdSession'] || ''</span><br><span class="line"> }),</span><br><span class="line"> Taro.setStorage({</span><br><span class="line"> key: 'uid',</span><br><span class="line"> data: data['uid'] || ''</span><br><span class="line"> })</span><br><span class="line"> ])</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * 简易封装网络请求</span><br><span class="line"> * // NOTE 需要注意 RN 不支持 *StorageSync,此处用 async/await 解决</span><br><span class="line"> * @param {*} options</span><br><span class="line"> */</span><br><span class="line">export default async function fetch(options) {</span><br><span class="line"> const {</span><br><span class="line"> url,</span><br><span class="line"> payload,</span><br><span class="line"> method = 'GET',</span><br><span class="line"> showToast = true</span><br><span class="line"> } = options</span><br><span class="line"> const token = await getStorage('token')</span><br><span class="line"> const header = token ? {</span><br><span class="line"> 'WX-PIN-SESSION': token,</span><br><span class="line"> 'X-WX-3RD-Session': token</span><br><span class="line"> } : {}</span><br><span class="line"> if (method === 'POST') {</span><br><span class="line"> header['content-type'] = 'application/json'</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return Taro.request({</span><br><span class="line"> url,</span><br><span class="line"> method,</span><br><span class="line"> data: payload,</span><br><span class="line"> header</span><br><span class="line"> }).then(async (res) => {</span><br><span class="line"> const {</span><br><span class="line"> code,</span><br><span class="line"> data</span><br><span class="line"> } = res.data</span><br><span class="line"> if (code !== CODE_SUCCESS) {</span><br><span class="line"> if (code === CODE_AUTH_EXPIRED) {</span><br><span class="line"> await updateStorage({})</span><br><span class="line"> }</span><br><span class="line"> return Promise.reject(res.data)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (url === API_USER_LOGIN) {</span><br><span class="line"> await updateStorage(data)</span><br><span class="line"> }</span><br><span class="line"> return data</span><br><span class="line"> }).catch((err) => {</span><br><span class="line"> const defaultMsg = err.code === CODE_AUTH_EXPIRED ? '登录失效' : '请求异常'</span><br><span class="line"> if (showToast) {</span><br><span class="line"> Taro.showToast({</span><br><span class="line"> title: err && err.errorMsg || defaultMsg,</span><br><span class="line"> icon: 'none'</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return Promise.reject({</span><br><span class="line"> message: defaultMsg,</span><br><span class="line"> ...err</span><br><span class="line"> })</span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h3 id="3-3-常用-API-介绍"><a href="#3-3-常用-API-介绍" class="headerlink" title="3.3 常用 API 介绍"></a>3.3 常用 API 介绍</h3><ul> <li>授权</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><Button</span><br><span class="line"> className='btn-max-w'</span><br><span class="line"> plain</span><br><span class="line"> type='primary'</span><br><span class="line"> open-type='getUserInfo'</span><br><span class="line"> onGetUserInfo={this.handleUserInfo}</span><br><span class="line">></span><br><span class="line"> 授权</span><br><span class="line"></Button></span><br></pre></td></tr></table></figure> <ul> <li>获取位置</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Taro.getLocation({type:'gcj02'}).then(data=>console.log(data))</span><br></pre></td></tr></table></figure> <ul> <li>操作反馈</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Taro.showToast({</span><br><span class="line"> title: "成功",</span><br><span class="line"> icon: "success"</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">Taro.setTabBarBadge({ index: 1, text: "1" });</span><br><span class="line"></span><br><span class="line">Taro.showLoading({</span><br><span class="line"> title: "加载中..."</span><br><span class="line">}).then(res =></span><br><span class="line"> setTimeout(() => {</span><br><span class="line"> Taro.hideLoading();</span><br><span class="line"> }, 2000)</span><br><span class="line">);</span><br></pre></td></tr></table></figure> <h2 id="4、其他问题记录"><a href="#4、其他问题记录" class="headerlink" title="4、其他问题记录"></a>4、其他问题记录</h2><ul> <li>设置页面全屏</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// app.scss</span><br><span class="line">page {</span><br><span class="line"> height: 100%;</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <ul> <li>Css Modules 支持<br>配置 config/index.js 下的 h5 和 weapp 中的 module.cssModules 即可</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// css modules 功能开关与相关配置</span><br><span class="line">cssModules: {</span><br><span class="line"> enable: true, // 默认为 false,如需使用 css modules 功能,则设为 true</span><br><span class="line"> config: {</span><br><span class="line"> namingPattern: 'module', // 转换模式,取值为 global/module,下文详细说明</span><br><span class="line"> generateScopedName: '[name]__[local]___[hash:base64:5]'</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <ul> <li>配置路径别名<br>配置 config/index.js 下的 alias</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">alias: {</span><br><span class="line"> 'components': path.resolve(__dirname, '..', 'src/components'),</span><br><span class="line"> 'pages': path.resolve(__dirname, '..', 'src/pages'),</span><br><span class="line"> 'store': path.resolve(__dirname, '..', 'src/store'),</span><br><span class="line"> 'constants': path.resolve(__dirname, '..', 'src/constants'),</span><br><span class="line"> 'api': path.resolve(__dirname, '..', 'src/api'),</span><br><span class="line"> 'assets': path.resolve(__dirname, '..', 'src/assets'),</span><br><span class="line"> 'utils': path.resolve(__dirname, '..', 'src/utils'),</span><br><span class="line"> },</span><br></pre></td></tr></table></figure> <ul> <li>多端兼容性问题,引入 lodash 包,支付宝不支持</li> </ul> <p>引入 utils.js 文件</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">global.Object = Object</span><br><span class="line">global.Array = Array</span><br><span class="line">global.Buffer = Buffer</span><br><span class="line">global.DataView = DataView</span><br><span class="line">global.Date = Date</span><br><span class="line">global.Error = Error</span><br><span class="line">global.Float32Array = Float32Array</span><br><span class="line">global.Float64Array = Float64Array</span><br><span class="line">global.Function = Function</span><br><span class="line">global.Int8Array = Int8Array</span><br><span class="line">global.Int16Array = Int16Array</span><br><span class="line">global.Int32Array = Int32Array</span><br><span class="line">global.Map = Map</span><br><span class="line">global.Math = Math</span><br><span class="line">global.Promise = Promise</span><br><span class="line">global.RegExp = RegExp</span><br><span class="line">global.Set = Set</span><br><span class="line">global.String = String</span><br><span class="line">global.Symbol = Symbol</span><br><span class="line">global.TypeError = TypeError</span><br><span class="line">global.Uint8Array = Uint8Array</span><br><span class="line">global.Uint8ClampedArray = Uint8ClampedArray</span><br><span class="line">global.Uint16Array = Uint16Array</span><br><span class="line">global.Uint32Array = Uint32Array</span><br><span class="line">global.WeakMap = WeakMap</span><br><span class="line">global.clearTimeout = clearTimeout</span><br><span class="line">global.isFinite = isFinite</span><br><span class="line">global.parseInt = parseInt</span><br><span class="line">global.setTimeout = setTimeout</span><br></pre></td></tr></table></figure> <ul> <li>打包微信,支付宝出问题</li> </ul> <p>npm 包版本不统一导致</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">"dependencies": {</span><br><span class="line"> "@tarojs/components": "3.0.18",</span><br><span class="line"> "@tarojs/react": "3.0.18",</span><br><span class="line"> "@tarojs/runtime": "3.0.18",</span><br><span class="line"> "@tarojs/taro": "3.0.18",</span><br><span class="line">},</span><br><span class="line">"devDependencies": {</span><br><span class="line"> "@tarojs/cli": "3.0.18",</span><br><span class="line"> "@tarojs/mini-runner": "3.0.18",</span><br><span class="line"> "@tarojs/webpack-runner": "3.0.18",</span><br><span class="line"> "babel-preset-taro": "3.0.18",</span><br><span class="line"> "eslint-config-taro": "3.0.18",</span><br><span class="line">},</span><br></pre></td></tr></table></figure> <ul> <li>全局 scss 设置,</li> </ul> <p>config.js 中可以设置,但是必须要重新启动才可以</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sass: {</span><br><span class="line"> resource: path.resolve(__dirname, '..', 'src/assets/styles/global.scss'),</span><br><span class="line">},</span><br></pre></td></tr></table></figure> <ul> <li>设置代理问题</li> </ul> <p>nginx 配合 fiddler 代理转发可以实现本地开发,app 端进行调试</p> <ul> <li>手机页面头部点击返回,不能触发页面生命周期</li> </ul> <p>可以通过 componentDidShow 或者 useDidShow 实现</p> <ul> <li>map 结构存储订单数据(存储二维数组)</li> </ul> <p>用法实现 order.get(status)</p> <ul> <li>不同业务线的状态扭转问题</li> </ul> <p>利用回调函数,函数式编程实现</p> <ul> <li>地图坐标系问题</li> </ul> <p>百度(bd09ll)、高德(gcj02)、腾讯(gcj02)</p> <ul> <li>轨迹渲染问题</li> </ul> <p>polyline, label, marker</p> <p>动态设置展示区域</p> <p>includePoints,并且通过 updateComponent 强制渲染,手机端和开发工具展示不一致问题</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">this.mapCtx.updateComponents({</span><br><span class="line"> markers,</span><br><span class="line"> includePoints,</span><br><span class="line"> polyline,</span><br><span class="line"> longitude: cLocation.lng,</span><br><span class="line"> latitude: cLocation.lat,</span><br><span class="line"> setting: {</span><br><span class="line"> showCompass: 0,</span><br><span class="line"> showScale: 0,</span><br><span class="line"> gestureEnable: 1,</span><br><span class="line"> tiltGesturesEnabled: 0,</span><br><span class="line"> },</span><br><span class="line"> })</span><br></pre></td></tr></table></figure> <ul> <li>处理多个不同任务的定时器,清除问题</li> </ul> <p>static 静态局部变量</p> <ul> <li>发布订阅者模式,处理登陆问题</li> </ul> <p>发布订阅的 service 类库封装</p> <ul> <li>定时器(汽车位置,订单状态,订单详情,支付状态),状态扭转类,支付类</li> </ul> <p>类封装</p> </div> <footer class="post-footer"> <div class="post-eof"></div> </footer> </article> <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN"> <link itemprop="mainEntityOfPage" href="http://example.com/2022/12/16/electron%E5%BC%80%E5%8F%91%E6%80%BB%E7%BB%93/"> <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person"> <meta itemprop="image" content="/images/portal.jpg"> <meta itemprop="name" content="Bruce Chen"> <meta itemprop="description" content="It's better to burn out than to fade away."> </span> <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"> <meta itemprop="name" content="Bruce Chen's Blog"> </span> <header class="post-header"> <h2 class="post-title" itemprop="name headline"> <a href="/2022/12/16/electron%E5%BC%80%E5%8F%91%E6%80%BB%E7%BB%93/" class="post-title-link" itemprop="url">electron开发总结</a> </h2> <div class="post-meta"> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-calendar"></i> </span> <span class="post-meta-item-text">发表于</span> <time title="创建时间:2022-12-16 14:45:10 / 修改时间:14:49:44" itemprop="dateCreated datePublished" datetime="2022-12-16T14:45:10+08:00">2022-12-16</time> </span> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="far fa-folder"></i> </span> <span class="post-meta-item-text">分类于</span> <span itemprop="about" itemscope itemtype="http://schema.org/Thing"> <a href="/categories/%E5%89%8D%E7%AB%AFUI%E6%A1%86%E6%9E%B6/" itemprop="url" rel="index"><span itemprop="name">前端UI框架</span></a> </span> </span> </div> </header> <div class="post-body" itemprop="articleBody"> <h1 id="一、用得到的-electron-开发总结"><a href="#一、用得到的-electron-开发总结" class="headerlink" title="一、用得到的 electron 开发总结"></a>一、用得到的 electron 开发总结</h1><p>Electron 是由 Github 开发,用 HTML,CSS 和 JavaScript 来构建跨平台桌面应用程序的一个开源库。 Electron 通过将 Chromium 和 Node.js 合并到同一个运行时环境中,并将其打包为 Mac,Windows 和 Linux 系统下的应用来实现这一目的。</p> <h3 id="添加启动动画"><a href="#添加启动动画" class="headerlink" title="添加启动动画"></a>添加启动动画</h3><p>由于 Electron 第一次启动比较慢,需要一些启动动画提高下用户体验。在主进程添加以下代码</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">const electron = require('electron')</span><br><span class="line">const app = electron.app</span><br><span class="line">const BrowserWindow = electron.BrowserWindow</span><br><span class="line">let mainWindow</span><br><span class="line"></span><br><span class="line">function createWindow(callback) {</span><br><span class="line"> mainWindow = new BrowserWindow({</span><br><span class="line"> width: 1400,</span><br><span class="line"> height: 900,</span><br><span class="line"> show: false</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> mainWindow.once('ready-to-show', () => {</span><br><span class="line"> if (callback){</span><br><span class="line"> callback();</span><br><span class="line"> }</span><br><span class="line"> mainWindow.show()</span><br><span class="line"> })</span><br><span class="line"> mainWindow.loadURL(`file://${__dirname}/build/index.html`);</span><br><span class="line">}</span><br><span class="line">app.on('ready', () => {</span><br><span class="line"> const useH = parseInt(0.865 * electron.screen.getPrimaryDisplay().workAreaSize.height);</span><br><span class="line"> const useW = parseInt(0.625 * electron.screen.getPrimaryDisplay().workAreaSize.width);</span><br><span class="line"></span><br><span class="line"> let logoWindow = new BrowserWindow({</span><br><span class="line"> width: useW,</span><br><span class="line"> height: useH,</span><br><span class="line"> transparent: true,</span><br><span class="line"> frame: false,</span><br><span class="line"> center: true,</span><br><span class="line"> show: false</span><br><span class="line"> });</span><br><span class="line"> logoWindow.loadURL(`file://${__dirname}/build/logo/logo.html`);</span><br><span class="line"> logoWindow.once('ready-to-show', () => {</span><br><span class="line"> logoWindow.show();</span><br><span class="line"> });</span><br><span class="line"> const closeLogoWindow = () => {</span><br><span class="line"> logoWindow.close();</span><br><span class="line"> };</span><br><span class="line"> createWindow(closeLogoWindow);</span><br><span class="line">})</span><br></pre></td></tr></table></figure> <h3 id="实现自动更新"><a href="#实现自动更新" class="headerlink" title="实现自动更新"></a>实现自动更新</h3><p>使用 <strong>electron-builder</strong> 结合 <strong>electron-updater</strong> 实现自动更新。</p> <p>配置 package.json 文件</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">"build": {</span><br><span class="line"> "publish": [</span><br><span class="line"> {</span><br><span class="line"> "provider": "generic",</span><br><span class="line"> "url": "http://ossactivity.tongyishidai.com/"</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>主进程添加自动更新检测和事件监听:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">import { autoUpdater } from "electron-updater"</span><br><span class="line">import { ipcMain } from "electron"</span><br><span class="line"></span><br><span class="line">function updateHandle() {</span><br><span class="line"> let message = {</span><br><span class="line"> error: '检查更新出错',</span><br><span class="line"> checking: '正在检查更新……',</span><br><span class="line"> updateAva: '检测到新版本,正在下载……',</span><br><span class="line"> updateNotAva: '现在使用的就是最新版本,不用更新',</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> autoUpdater.setFeedURL('http://ossactivity.tongyishidai.com/');</span><br><span class="line"> autoUpdater.on('error', function (error) {</span><br><span class="line"> console.log(error)</span><br><span class="line"> sendUpdateMessage(message.error)</span><br><span class="line"> });</span><br><span class="line"> autoUpdater.on('checking-for-update', function () {</span><br><span class="line"> sendUpdateMessage(message.checking)</span><br><span class="line"> });</span><br><span class="line"> autoUpdater.on('update-available', function (info) {</span><br><span class="line"> sendUpdateMessage(message.updateAva)</span><br><span class="line"> });</span><br><span class="line"> autoUpdater.on('update-not-available', function (info) {</span><br><span class="line"> sendUpdateMessage(message.updateNotAva)</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> // 更新下载进度事件</span><br><span class="line"> autoUpdater.on('download-progress', function (progressObj) {</span><br><span class="line"> mainWindow.webContents.send('downloadProgress', progressObj)</span><br><span class="line"> })</span><br><span class="line"> autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {</span><br><span class="line"></span><br><span class="line"> ipcMain.on('isUpdateNow', (e, arg) => {</span><br><span class="line"> console.log(arguments);</span><br><span class="line"> console.log("开始更新");</span><br><span class="line"> //some code here to handle event</span><br><span class="line"> autoUpdater.quitAndInstall();</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> mainWindow.webContents.send('isUpdateNow')</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> ipcMain.on("checkForUpdate",()=> {</span><br><span class="line"> //执行自动更新检查</span><br><span class="line"> autoUpdater.checkForUpdates();</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 通过main进程发送事件给renderer进程,提示更新信息</span><br><span class="line">function sendUpdateMessage(text) {</span><br><span class="line"> mainWindow.webContents.send('message', text)</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <blockquote> <p>注意:</p> <ul> <li>在添加自动更新检测和事件监听之后,在主进程 createWindow 中需要调用一下 updateHandle()。</li> <li>这个 autoUpdater 不是 electron 中的 autoUpdater,是 electron-updater 的 autoUpdater。(这里曾报错曾纠结一整天)</li> </ul> </blockquote> <p>在视图(View)层中触发自动更新,并添加自动更新事件的监听。</p> <p>触发自动更新:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipcRenderer.send("checkForUpdate");</span><br></pre></td></tr></table></figure> <p>监听自动更新事件:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">ipcRenderer.on("message", (event, text) => {</span><br><span class="line"> console.log(text)</span><br><span class="line">});</span><br><span class="line">ipcRenderer.on("downloadProgress", (event, progressObj)=> {</span><br><span class="line"> console.log(progressObj.percent)</span><br><span class="line"> this.setState({</span><br><span class="line"> downloadPercent: parseInt(progressObj.percent) || 0</span><br><span class="line"> })</span><br><span class="line">});</span><br><span class="line">ipcRenderer.on("isUpdateNow", () => {</span><br><span class="line"> ipcRenderer.send("isUpdateNow");</span><br><span class="line">});</span><br></pre></td></tr></table></figure> <p>为避免多次切换页面造成监听的滥用,切换页面前必须移除监听事件:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipcRenderer.removeAll(["message", "downloadProgress", "isUpdateNow"]);</span><br></pre></td></tr></table></figure> <blockquote> <p>参考地址 segmentfault.com/a/119000001…</p> </blockquote> <h3 id="使用-electron-builder-打包-exe-安装包"><a href="#使用-electron-builder-打包-exe-安装包" class="headerlink" title="使用 electron-builder 打包 exe 安装包"></a>使用 electron-builder 打包 exe 安装包</h3><p>package.json 文件配置如下</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line">"build": {</span><br><span class="line"> "productName": "CubeScratch",</span><br><span class="line"> "appId": "org.develar.CubeScratch",</span><br><span class="line"> "publish": [</span><br><span class="line"> {</span><br><span class="line"> "provider": "generic",</span><br><span class="line"> "url": "http://ossactivity.tongyishidai.com/"</span><br><span class="line"> }</span><br><span class="line"> ],</span><br><span class="line"> "win": {</span><br><span class="line"> "icon": "icon.ico",</span><br><span class="line"> "artifactName": "${productName}_Setup_${version}.${ext}",</span><br><span class="line"> "target": [</span><br><span class="line"> "nsis"</span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> "mac": {</span><br><span class="line"> "icon": "icon.icns"</span><br><span class="line"> },</span><br><span class="line"> "nsis": {</span><br><span class="line"> "oneClick": false,</span><br><span class="line"> "allowElevation": true,</span><br><span class="line"> "allowToChangeInstallationDirectory": true,</span><br><span class="line"> "installerIcon": "icon.ico",</span><br><span class="line"> "uninstallerIcon": "icon.ico",</span><br><span class="line"> "installerHeaderIcon": "icon.ico",</span><br><span class="line"> "createDesktopShortcut": true,</span><br><span class="line"> "createStartMenuShortcut": true,</span><br><span class="line"> "shortcutName": "CubeScratch"</span><br><span class="line"> },</span><br><span class="line"> "directories": {</span><br><span class="line"> "buildResources": "resources",</span><br><span class="line"> "output": "release"</span><br><span class="line"> },</span><br><span class="line"> "dmg": {</span><br><span class="line"> "contents": [</span><br><span class="line"> {</span><br><span class="line"> "x": 410,</span><br><span class="line"> "y": 150,</span><br><span class="line"> "type": "link",</span><br><span class="line"> "path": "/Applications"</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> "x": 130,</span><br><span class="line"> "y": 150,</span><br><span class="line"> "type": "file"</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <blockquote> <p>参考地址 juejin.im/post/684490…</p> </blockquote> <h3 id="windows-平台-serialport-串口编译"><a href="#windows-平台-serialport-串口编译" class="headerlink" title="windows 平台 serialport 串口编译"></a>windows 平台 serialport 串口编译</h3><p>如果需要 serialport 作为 Electron 项目的依赖项,则必须针对项目使用的 Electron 版本对其进行编译。<br>对于大多数最常见的用例(标准处理器平台上的 Linux,Mac,Windows),我们使用 prebuild 来编译和发布库的二进制文件。<br>使用 nodejs 进行编译 node-gyp 需要使用 Python 2.x,因此请确保已安装它,并且在所有操作系统的路径中。Python 3.x 无法正常工作。<br>安装 windows 构建工具和配置想省时省力请选择以下方案:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install --global --production windows-build-tools</span><br></pre></td></tr></table></figure> <p>上边命令决定串口编译是否成功。安装过程非常缓慢,安装完成就等于串口编译成功了 99%。<br>手动安装工具和配置请看<a target="_blank" rel="noopener" href="https://github.com/nodejs/node-gyp/">https://github.com/nodejs/node-gyp/</a><br>接下来用 electron-rebuild 包重建模块以适配 Electron。这个包可以自动识别当前 Electron 版本,为你的应用自动完成下载 headers、重新编译原生模块等步骤。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">"scripts": {</span><br><span class="line"> "rebuild": "electron-rebuild"</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>执行以下命令完成串口编译</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run rebuild</span><br></pre></td></tr></table></figure> <blockquote> <p>注意:</p> <ul> <li>编译过的串口不同系统不可通用,需在各平台重新编译</li> <li>windows 系统最好是正版,或净化版。否正很有可能安装失败。</li> </ul> </blockquote> <h1 id="二、Electron:使用-React-作为-Renderer"><a href="#二、Electron:使用-React-作为-Renderer" class="headerlink" title="二、Electron:使用 React 作为 Renderer"></a>二、Electron:使用 React 作为 Renderer</h1><h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1. 前言"></a>1. 前言</h2><p>通过之前对 Electron 的了解,现在多少也能看出 Electron 的一些特点。</p> <p>其中,Main Process 用来调度各个 Renderer Process;而各个 Renderer Process 实际上就是 web+(比 web 功能要强,姑且就允许我这么叫一次吧),他们除了 web 应用本身所拥有的特征之外,还可以通过引入 Electron 来扩展,当然也可以使用 NodeJs 的特性,比如在 React 中使用 fs.writeFile()(听起来很诡异)。</p> <p>如果之前的意见记不清楚了的话,可以再去翻翻看《<a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000021820641">Electron:Web 应用桌面化</a>》、《<a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000021843332">Electron:主进程与渲染器进程</a>》。</p> <p>本篇呢,主要是为了来总结一下如何将 React 与 Electron 结合起来,让编码更加高效。</p> <h2 id="2-为什么要引入-React-?"><a href="#2-为什么要引入-React-?" class="headerlink" title="2. 为什么要引入 React ?"></a>2. 为什么要引入 React ?</h2><p>实际上在最初学习的时候直接在 Electron 的应用中写 html+js+css 感觉也不错,但是当场景稍微复杂起来后就会感觉写起来比较繁琐。</p> <p>另外还有就是在切换页面的场景,如果复用渲染器进程而直接采用切换 Url 的方式虽然也可以完成导航,但是会有一个“白屏时间”让人挺不舒服的。如果每个页面都起一个自己的渲染器进程,由于新窗口的启动有一个过渡,因此不会感觉到“白屏时间”,但是总感觉每个页面都起一个渲染器进程也并不是适用于任意位置。</p> <p>所以说 SPA 是非常合适的一个选择,至于选什么框架并不重要。</p> <h3 id="最基础的整合"><a href="#最基础的整合" class="headerlink" title="最基础的整合"></a>最基础的整合</h3><p>通过分别在 Electron 中添加 React 和在 React 中添加 Electron 作比较之后,我发现在 React 中添加 Electron 仿佛要简单一些(也可能目前场景比较简单 🤔)。</p> <h3 id="新建一个-React-App"><a href="#新建一个-React-App" class="headerlink" title="新建一个 React App"></a>新建一个 React App</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn create react-app electron-react</span><br></pre></td></tr></table></figure> <p>结束之后跑起来这个站点,假设站点地址是 <a href="http://localhost:3000,成功执行就可以了,保持这个执行状态不用关闭。">http://localhost:3000,成功执行就可以了,保持这个执行状态不用关闭。</a></p> <h3 id="添加-Electron"><a href="#添加-Electron" class="headerlink" title="添加 Electron"></a>添加 Electron</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add electron --dev</span><br></pre></td></tr></table></figure> <p>在 src/ 目录下新建 main.js 入口文件,内容如下:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">// src/main.js</span><br><span class="line">const { app, BrowserWindow } = require('electron')</span><br><span class="line"></span><br><span class="line">app.allowRendererProcessReuse = true</span><br><span class="line"></span><br><span class="line">function createWindow () {</span><br><span class="line"> let win = new BrowserWindow({</span><br><span class="line"> height: 500,</span><br><span class="line"> width: 800,</span><br><span class="line"> webPreferences: {</span><br><span class="line"> nodeIntegration: true</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> win.loadURL(`http://localhost:3000`)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">app.on('ready', () => createWindow())</span><br></pre></td></tr></table></figure> <p>之后修改 package.json 文件,改一下入口文件,加一个 script:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">"main": "src/main.js",</span><br><span class="line">"scripts": {</span><br><span class="line"> "start:main": "electron ."</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p>执行</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn start:main</span><br></pre></td></tr></table></figure> <p>不出意外应该是成功了的。</p> <h3 id="在-React-中使用-Electron"><a href="#在-React-中使用-Electron" class="headerlink" title="在 React 中使用 Electron"></a>在 React 中使用 Electron</h3><p>默认在 React 中 使用 require(‘electron’) 是不行的,会报错:</p> <p><img src="https://segmentfault.com/img/bVbD0ug" alt="image"></p> <p>报错的原因是通过 <strong>create-react-app</strong> 创建的应用中 target 是 web 环境 ,因此不能识别 node api。</p> <p>下面通过两种途径可以来解决这个问题。</p> <h4 id="1-绕过-Webpack-的检查机制解决"><a href="#1-绕过-Webpack-的检查机制解决" class="headerlink" title="1. 绕过 Webpack 的检查机制解决"></a>1. 绕过 Webpack 的检查机制解决</h4><p>比较简单的处理办法就是使用 <code>window.require('electron')</code> 代替 <code>require('electron')</code>。window 对象在 electron 中是指向 global 的,所以它在执行时可以找到 require 函数。</p> <h4 id="2-修改-React-Webpack-配置解决"><a href="#2-修改-React-Webpack-配置解决" class="headerlink" title="2. 修改 React Webpack 配置解决"></a>2. 修改 React Webpack 配置解决</h4><p>为了验证上面报错原因的猜测,我尝试 eject 了一个项目去观察,配置中的 target 确实是缺失的,那么理论上把这个 target 修改为对应的 target 就可以支持 <code>require('electron')</code> 了。</p> <p><a target="_blank" rel="noopener" href="https://www.webpackjs.com/configuration/target/">你可以点击这里来查看 target 都有哪些</a></p> <p>显然我们需要的是:<code>electron-renderer</code>。</p> <p>为了修改这个 target,首先需要把 React 项目 <a target="_blank" rel="noopener" href="https://create-react-app.dev/docs/available-scripts#npm-run-eject">eject</a>:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run eject</span><br></pre></td></tr></table></figure> <blockquote> <p>Note: this is a one-way operation. Once you eject, you can’t go back!(此过程不可逆,请谨慎操作!)</p> </blockquote> <p>弹出后,打开 <code>config/webpack.config.js</code> 文件,大约在 <strong>130</strong> 行左右,添加代码片段中标记的那一句,我截取了一小段代码:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">return {</span><br><span class="line"> target: 'electron-renderer', // <- 添加这一句</span><br><span class="line"> mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',</span><br><span class="line"> // Stop compilation early in production</span><br><span class="line"> bail: isEnvProduction,</span><br><span class="line"> devtool: isEnvProduction</span><br><span class="line"> ? shouldUseSourceMap</span><br></pre></td></tr></table></figure> <p>好了,重新启动后 <code>require('electron')</code> 就生效了,值得注意的是 <strong>无法直接在 Chrome 中运行,必须从 Electron 中打开。</strong></p> <h3 id="取消-React-默认打开浏览器的行为"><a href="#取消-React-默认打开浏览器的行为" class="headerlink" title="取消 React 默认打开浏览器的行为"></a>取消 React 默认打开浏览器的行为</h3><h4 id="方法-1"><a href="#方法-1" class="headerlink" title="方法 1"></a>方法 1</h4><ul> <li><strong>bash</strong>:</li> </ul> <p>bash 可能还是比较常用的一个 terminal</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># 设置临时环境变量</span><br><span class="line">BROWSER=none</span><br><span class="line"># 执行</span><br><span class="line">yarn start:render</span><br><span class="line"></span><br><span class="line">#or</span><br><span class="line"></span><br><span class="line"># 连续命令</span><br><span class="line">BROWSER=none && yarn start:render</span><br></pre></td></tr></table></figure> <ul> <li><strong>cmd</strong>:</li> </ul> <p>如果你用的是 cmd,你可以使用下面的方式</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># 设置变量</span><br><span class="line">set "BROWSER=none"</span><br><span class="line"># 执行</span><br><span class="line">yarn start:render</span><br><span class="line"></span><br><span class="line"># or</span><br><span class="line"></span><br><span class="line"># 连续命令</span><br><span class="line">set "BROWSER=none" && yarn start:render</span><br></pre></td></tr></table></figure> <p>设置临时环境变量只需要一次就可以了,该变量只在当前打开的 cmd 中有效。</p> <ul> <li><strong>pwsh</strong>:</li> </ul> <p>power shell 与 cmd 有所不同</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"># 设置变量</span><br><span class="line">$env:BROWSER="none"</span><br><span class="line"># 执行</span><br><span class="line">yarn start:render</span><br><span class="line"></span><br><span class="line"># or</span><br><span class="line"></span><br><span class="line"># 连续命令(我这里尝试会卡住)</span><br><span class="line">($env:BROWSER="none") -and (yarn start:render)</span><br></pre></td></tr></table></figure> <h4 id="方法-2"><a href="#方法-2" class="headerlink" title="方法 2"></a>方法 2</h4><p>在根目录添加一个 .env 文件:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// .env</span><br><span class="line">BROWSER=none</span><br></pre></td></tr></table></figure> <p>之后正常执行就可以。</p> <h2 id="3-打包-🛠️"><a href="#3-打包-🛠️" class="headerlink" title="3. 打包 🛠️"></a>3. 打包 🛠️</h2><p>没有打包功能的 Electron App 就没有意义,毕竟软件做出来是给人用的 🙃。</p> <p>Electron 方面推荐有两个打包工具:</p> <ul> <li>electron-forge</li> <li>electron-builder<br>相对来讲,electron-forge 更加适合于从零开始,直接基于 electron-forge 提供的模板项目开始开发 electron 应用。而 electron-builder 适合于对已有项目的打包。</li> </ul> <p>相信一般来到这里的大家或者我自己也是,已经准备好了一个项目,就差打包了,所以大家一般更倾向于使用 electron-builder。</p> <h3 id="安装-electron-builder"><a href="#安装-electron-builder" class="headerlink" title="安装 electron-builder"></a>安装 electron-builder</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add --dev electron-builder</span><br></pre></td></tr></table></figure> <h3 id="配置-electron-builder"><a href="#配置-electron-builder" class="headerlink" title="配置 electron-builder"></a>配置 electron-builder</h3><p>在根目录创建一个 <strong>electron-builder.yml</strong> 的配置文件,用于配置 <strong>electron-builder</strong> 打包相关内容。</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">appId: com.example.app</span><br><span class="line">copyright: ©2020 bey6</span><br><span class="line">productName: bey6</span><br><span class="line"># asar 加密</span><br><span class="line">asar: false</span><br><span class="line">extends: null</span><br><span class="line"></span><br><span class="line"># 目录</span><br><span class="line">directories:</span><br><span class="line"> buildResources: assets/</span><br><span class="line"> output: dist/</span><br><span class="line"></span><br><span class="line">files:</span><br><span class="line"> - package.json</span><br><span class="line"> - build/</span><br><span class="line"> - node_modules/</span><br><span class="line"> - src/main.js</span><br><span class="line"></span><br><span class="line">dmg:</span><br><span class="line"> contents:</span><br><span class="line"> - type: link</span><br><span class="line"> path: /Applications</span><br><span class="line"> x: 410</span><br><span class="line"> y: 150</span><br><span class="line"> - type: file</span><br><span class="line"> x: 130</span><br><span class="line"> y: 150</span><br><span class="line">win:</span><br><span class="line"> # 目标类型</span><br><span class="line"> target: nsis</span><br><span class="line"></span><br><span class="line">nsis:</span><br><span class="line"> oneClick: false</span><br><span class="line"> # 允许修改安装路径</span><br><span class="line"> allowToChangeInstallationDirectory: true</span><br><span class="line"> # 安装给所有用户</span><br><span class="line"> perMachine: true</span><br></pre></td></tr></table></figure> <h3 id="修改-package-json"><a href="#修改-package-json" class="headerlink" title="修改 package.json"></a>修改 package.json</h3><ul> <li>修改 homepage</li> <li>scripts 中添加一个打包 win64 的脚本</li> </ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">"homepage": ".",</span><br><span class="line">"scripts": {</span><br><span class="line"> "build:win64": "yarn build && electron-builder --win --x64"</span><br><span class="line">},</span><br></pre></td></tr></table></figure> <h3 id="修改-Main-Process"><a href="#修改-Main-Process" class="headerlink" title="修改 Main Process"></a>修改 Main Process</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">// src/main.js</span><br><span class="line">const { app, BrowserWindow, ipcMain } = require('electron')</span><br><span class="line">const path = require('path')</span><br><span class="line"></span><br><span class="line">app.allowRendererProcessReuse = true</span><br><span class="line"></span><br><span class="line">function createWindow () {</span><br><span class="line"> let win = new BrowserWindow({</span><br><span class="line"> height: 500,</span><br><span class="line"> width: 800,</span><br><span class="line"> webPreferences: {</span><br><span class="line"> nodeIntegration: true</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> // 修改这里</span><br><span class="line"> if (process.env.NODE_ENV === 'dev') win.loadURL(`http://localhost:3000`)</span><br><span class="line"> else win.loadFile(path.resolve(__dirname, '../build/index.html'))</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// ...</span><br></pre></td></tr></table></figure> <h3 id="打包"><a href="#打包" class="headerlink" title="打包"></a>打包</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn build:win64</span><br></pre></td></tr></table></figure> <p>打包结束后会在根目录多出来一个 dist/ 目录,执行其中的 .exe 即可完成安装。</p> </div> <footer class="post-footer"> <div class="post-eof"></div> </footer> </article> <nav class="pagination"> <a class="extend prev" rel="prev" href="/page/2/"><i class="fa fa-angle-left" aria-label="上一页"></i></a><a class="page-number" href="/">1</a><a class="page-number" href="/page/2/">2</a><span class="page-number current">3</span><a class="page-number" href="/page/4/">4</a><span class="space">…</span><a class="page-number" href="/page/9/">9</a><a class="extend next" rel="next" href="/page/4/"><i class="fa fa-angle-right" aria-label="下一页"></i></a> </nav> </div> <script> window.addEventListener('tabs:register', () => { let { activeClass } = CONFIG.comments; if (CONFIG.comments.storage) { activeClass = localStorage.getItem('comments_active') || activeClass; } if (activeClass) { let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`); if (activeTab) { activeTab.click(); } } }); if (CONFIG.comments.storage) { window.addEventListener('tabs:click', event => { if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; let commentClass = event.target.classList[1]; localStorage.setItem('comments_active', commentClass); }); } </script> </div> <div class="toggle sidebar-toggle"> <span class="toggle-line toggle-line-first"></span> <span class="toggle-line toggle-line-middle"></span> <span class="toggle-line toggle-line-last"></span> </div> <aside class="sidebar"> <div class="sidebar-inner"> <ul class="sidebar-nav motion-element"> <li class="sidebar-nav-toc"> 文章目录 </li> <li class="sidebar-nav-overview"> 站点概览 </li> </ul> <!--noindex--> <div class="post-toc-wrap sidebar-panel"> </div> <!--/noindex--> <div class="site-overview-wrap sidebar-panel"> <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person"> <img class="site-author-image" itemprop="image" alt="Bruce Chen" src="/images/portal.jpg"> <p class="site-author-name" itemprop="name">Bruce Chen</p> <div class="site-description" itemprop="description">It's better to burn out than to fade away.</div> </div> <div class="site-state-wrap motion-element"> <nav class="site-state"> <div class="site-state-item site-state-posts"> <a href="/archives/"> <span class="site-state-item-count">83</span> <span class="site-state-item-name">日志</span> </a> </div> <div class="site-state-item site-state-categories"> <a href="/categories/"> <span class="site-state-item-count">7</span> <span class="site-state-item-name">分类</span></a> </div> <div class="site-state-item site-state-tags"> <a href="/tags/"> <span class="site-state-item-count">3</span> <span class="site-state-item-name">标签</span></a> </div> </nav> </div> <iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=1440429303&auto=1&height=66"></iframe> <div class="links-of-author motion-element"> <span class="links-of-author-item"> <a href="https://github.com/jschentt" title="GitHub → https://github.com/jschentt" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a> </span> <span class="links-of-author-item"> <a href="mailto:jschentt@gmail.com" title="E-Mail → mailto:jschentt@gmail.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a> </span> <span class="links-of-author-item"> <a href="https://twitter.com/BruceCh56742806" title="Twitter → https://twitter.com/BruceCh56742806" rel="noopener" target="_blank"><i class="fab fa-twitter fa-fw"></i>Twitter</a> </span> <span class="links-of-author-item"> <a href="https://www.facebook.com/jinsheng.chen.5439" title="Facebook → https://www.facebook.com/jinsheng.chen.5439" rel="noopener" target="_blank"><i class="fab fa-facebook fa-fw"></i>Facebook</a> </span> </div> </div> </div> </aside> <div id="sidebar-dimmer"></div> </div> </main> <footer class="footer"> <div class="footer-inner"> <div class="copyright"> © <span itemprop="copyrightYear">2022</span> <span class="with-love"> <i class="fa fa-heart"></i> </span> <span class="author" itemprop="copyrightHolder">Bruce Chen</span> </div> <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://pisces.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Pisces</a> 强力驱动 </div> <div class="busuanzi-count"> <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script> <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;"> <span class="post-meta-item-icon"> <i class="fa fa-user"></i> </span> <span class="site-uv" title="总访客量"> <span id="busuanzi_value_site_uv"></span> </span> </span> <span class="post-meta-divider">|</span> <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;"> <span class="post-meta-item-icon"> <i class="fa fa-eye"></i> </span> <span class="site-pv" title="总访问量"> <span id="busuanzi_value_site_pv"></span> </span> </span> </div> </div> </footer> </div> <script src="/lib/anime.min.js"></script> <script src="/lib/velocity/velocity.min.js"></script> <script src="/lib/velocity/velocity.ui.min.js"></script> <script src="/js/utils.js"></script> <script src="/js/motion.js"></script> <script src="/js/schemes/pisces.js"></script> <script src="/js/next-boot.js"></script> <script src="/js/local-search.js"></script> </body> </html>