培训理念
- 重点
- 易懂
- 实用
第一课:项目讲解
前端学习计划
1.了解前端技术栈
1. react:前端开发语言(着重学习)
- React是用于构建用户界面的JavaScript框架,用于构建高效、快速的用户界面。React 中一切都是组件。
- 虚拟dom
2. webpack:前端打包工具
- 一个开源的前端打包工具,将你的js、css、img、svg以更优的方式进行解析加载,配置灵活,功能强大
3. ant-design:商户侧前端使用的UI组件库(着重使用)
- 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品
4. dva:react异步请求以及redux全局数据流
- dva 首先是一个基于 redux 和 redux-saga的数据流方案
- 易学易用,仅有 6 个 api,对 redux 用户尤其友好
5. es6:JavaScript语言的下一代标准
- es5语言的升级,提供更加强大的语法糖,开发更加便捷,不但能减少代码量,还能解决原来没有完善的问题
- 目前ES6也是使用最多的javaScript语言标准
6.axios:一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
2.项目结构理解
1. node_modules:前端组件库管理包
- 安装node后用来存放用包管理工具下载安装的包的文件夹,例如react、webpack、轮播图插件等各种库,以便项目使用,类似后端的nuget
2. package.json:项目组件库版本管理文件(如果我们用自己开发的组件库,也是npm组件库的读取文件)
- 1.管理node_modules组件库版本的配置文件(常用)
- 2.当你开发自己的组件库,它也是作为组件库的版本管理配置文件(组件库开发)
3. .babellrc:es6语法解析
- 下一代Javascript的标准,浏览器因版本的不同对此会有兼容性问题
- .babelrc是Babel的配置文件,放在项目根目录下,结合webpack使用
4. webpack配置文件
开发环境和生产环境打包希望看到的效果肯定不一样,比如生产环境需要压缩代码,去除注释,开发环境需要热更新,不压缩代码,所以需要存在一套开发环境版本和一套生产环境版本
-
webpack.config.common.js:webpack公用文件
-
webpack.config.dev.js:webpack开发环境文件
-
webpack.config.prod.js:webpack生产环境文件
node_modules和package.json
7. src:源码目录components、configs、models、services、utils、index.ejs、index.js、Router.js
3.src源码目录讲解
开发一个页面的流程:
- components文件夹新建js组件、less样式,进行前端静态页开发并连接dva
- router.js路由文件引入页面组件,并放置到路由上
- models文件夹写入models文件
- services文件夹写入services文件
- index.js注册对应的models文件或者在路由注册
- 前后端接口开发联调
3.1 component
功能:存放开发的业务组件
包含:
- js
- less
- imgs
3.2 models
定义每个页面交互的命名空间以及dispatch发起的行为方法和数据的处理
3.3 services
异步请求的中转文件
3.4 untils工具库
定义自己写的工具库,例如异步请求axios、数据截取的公用函数js
3.5 index.js
- 项目入口js文件
- 引用全局样式
- 引用models文件connect连接组件
- 引用路由
3.6 index.ejs
- 单页应用入口html文件
3.7 router.js
页面路由
第二课:模块包管理工具之npm
npm大致流程
- 使用 npm publish 把代码提交到 repository 上,分别取名 jquery、bootstrap 和 underscore
- 社区里的其他人如果想使用这些代码,运行 npm install就把 jquery、bootstrap 和 underscore 写到 package.json 里
- 在package.json写入包名以及版本,然后使用npm install也可以进行安装
- 下载完的代码出现在 node_modules 目录里,就可以随意使用了。
这些可以被使用的代码被叫做「包」(package),这就是 npm名字的由来:Node Package(包) Manager(管理器)。
核心概念
-
- 组件库下载 2.组件库上传 3.稳定成熟的版本管理
package.json 简要介绍
- name - 包名;
- version - 包的版本号;
- description - 包的描述;
- homepage - 包的官网 url;
- author - 包的作者姓名;
- contributors - 包的其他贡献者姓名;
- dependencies - 依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下;
- devDependencies:开发环境下,项目所需依赖。
- repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上;
- main - main 字段指定了程序的主入口文件,require('moduleName') 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js;
- keywords - 关键字;
- bin:用来指定各个内部命令对应的可执行文件的路径
dependenices
-
通过命令npm install/i packageName -S/--save把包装在此依赖项里。如果没有指定版本,直接写一个包的名字,则安装当前npm仓库中这个包的最新版本。如果要指定版本的,可以把版本号写在包名后面,比如npm i vue@3.0.1 -S。
-
从npm 5.x开始,可以不用手动添加-S/--save指令,直接执行npm i packageName把依赖包添加到dependencies中去。
-
不仅开发环境能使用,生产环境也能使用
-
例如react、antd
devDependenices
- 只会在开发环境下依赖的模块,生产环境不会被打入包内
- 有一些包有可能你只是在开发环境中用到,例如你用于检测代码规范的 eslint ,用于进行测试的 jest ,用户使用你的包时即使不安装这些依赖也可以正常运行,反而安装他们会耗费更多的时间和资源,所以你可以把这些依赖添加到 devDependencies 中,这些依赖照样会在你本地进行 npm install 时被安装和管理,但是不会被安装到生产环境:
- 例如less-loader webpack
cnpm npm yarn的比较
npm
- 5.0之前版本很多问题,下载速度慢,不能锁包
- 主流,社区庞大,各大网站库都是使用npm进行安装
- 成熟、稳定
- 完善了很多问题(锁包、本地缓存),慢慢接近yarn
cnpm
优点:
- cnpm是个中国版的npm,是淘宝定制的 cnpm,下载速度极快
缺点:
- 每隔10分钟去刷新npm库同步到cnpm
- 只会读取取package.json,不会读取package.lock.json
- 安装也不会生成或改变package.lock.json
yarn
优点:
-
(1)并行安装:无论 npm 还是Yarn在执行包的安装时,都会执行一系列任务。npm是按照队列执行每个package,也就是说必须要等到当前package安装完成之后,才能继续后面的安装。而 Yarn 是并行执行所有任务,提高了性能。
-
(2)离线模式:如果之前已经安装过一个软件包,用Yarn再次安装时直接从缓存中获取,就不用像npm那样再从网络下载了。
-
集成了npm基本所有的优点
npm和yarn命令对比
总结:
如果是安装层面更建议使用yarn进行安装模块包,速度快,稳定性强,只要对里面各种机制熟练使用,就可以把三者用得如鱼得水,不然就会出各种难以预料的问题
特性
- 版本管理:大中小三个版本,中小版本可以向下兼容
- 锁包机制:控制npm install安装的版本是锁定的版本,防止出现不同开发者包的版本不同出现bug
- 可以不通过npm install的方式直接在node_modules使用组件库
- node_modules找寻规则
第三课之webpack基础入门
webpack基础入门
- 什么是Webpack(上图)
Webpack是一个开源的前端打包工具,可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求,日常开发最直观能用到的(webpack本身只能处理js,如果其他类型就需要loader进行转换)
-
- 模块化代码,定义的全局变量不会影响,将复杂的代码细化,代码复用性强使用方便,js、css导入导出,按需加载(使用插件、模块支持按需加载)
-
- less、scss、es6、typescript等提升开发效率的语法预处理转换成浏览器识别的js、css,图片转base64
-
- 开发环境热更新(改变代码保存之后浏览器自动更新最新代码)
-
- 生产环境代码压缩、混淆、公共代码拆分
使用
webpack 配置
- entry
- output
- mode
- loaders
- plugins
1.entry:项目的入口文件
根据应用程序的特定需求,可以以多种方式配置 entry 属性
- 写法一 字符串,单入口文件,输出一个文件
- 写法二 多页应用配置,打包出来的js可以在对应的多页面使用
- 写法三 数组,相当于将两个文件打包到一个文件里,输出一个文件
项目中更多的用的是写法三
2.output:输出文件
在 webpack 中配置 output 属性的最低要求是,将它的值设置为一个对象,包括以下两点
- filename 用于输出文件的文件名。
- 目标输出目录 path 的绝对路径。
// 我们项目的写法
const config = {
output: {
// 打包后的路径
path: join(__dirname, directory.production.envName),
// 打包出的文件前面的前缀
publicPath: general.publicPath + '/',
// 因为项目有代码分割等插件会分离对应文件,所以使用[name]可以加载对应名称
filename: directory.production.resource + '/' + directory.production.javascript + '/' + '[name]-[hash:10].js',
// 每个页面异步加载的js,例如对应页面通过import引入过来的组件、函数打包不是在入口文件指定的,但是又需要引入的,设置hash可以防止缓存
chunkFilename: directory.production.resource + '/' + directory.production.javascript + '/' + '[name]-[contenthash:10].js',
},
};
一句话总结
- filename 指列在 entry 中,打包后输出的文件的名称。
- chunkFilename 指未列在 entry 中,却又需要被打包出来的文件的名称。如果不设置会给默认值
hash
- hash每次修改任何一个文件,所有文件名的hash值都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效
chunkhash
- chunkhash根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响,
- 但是生产环境中我们会用webpack的插件,将css代码打单独提取出来打包。这时候chunkhash的方式就不够灵活,因为只要同一个chunk里面的js修改后,css的chunk的hash也会跟随着改动。因此我们需要contenthash。
contenthash
- contenthash是针对文件内容级别的,只有你自己模块的内容变了,那么hash值才改变
项目目前运行部署机制
- 只要公用函数库不变,只改业务代码的话只会,webpack打包只会修改对应业务代码的js以及hash值,没改过的文件不会受影响,之前文件的缓存就不会丢失
3.mode
// 方式1 webpack.config.js写法
module.exports = {
mode: 'production'
};
// 方式2 package.json的scripts脚本命令写法
webpack --mode=production
// 启用对应开发环境默认的插件
development 会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。
// 启用对应生产环境默认的插件
production 会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin.
4.loader
loader 用于对模块的源代码进行转换,loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件
- js、css、img、svg
module.exports = {
module:{
rules:[{
test:/.js$/,
use:[{
loader:'babel-loader',
options:{
presets:['react']
}
}]
}]
}
}
5.plugin插件
插件目的在于解决 loader 无法实现的其他事。
const HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装
module.exports = {
plugins: [
// 代码压缩
new webpack.optimize.UglifyJsPlugin(),
// 入口页面
new HtmlWebpackPlugin({template: './src/index.html'}),
// 拆分css
new MiniCssExtractPlugin({
filename: directory.production.resource + '/' + directory.production.style + '/' + '[name]-[contenthash:10].css',
chunkFilename: directory.production.resource + '/' + directory.production.style + '/' + '[name]-[contenthash:10].css',
})
]
}
loader和plugin有什么区别
- loader只专注于转化文件(transform)这一个领域,完成压缩,打包,语言翻译,loader是运行在NodeJS中,仅仅只是为了打包
- plugin也是为了扩展webpack的功能,资源的加载上,它的功能要更加丰富。从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务(例如:clean-webpack-plugin、html-webpack-plugin)
- loader运行在打包文件之前(loader为在模块加载时的预处理文件)
- plugins在整个编译周期都起作用。
案例讲解
案例一:打包js
- webpack直接进行js打包
案例二:打包css,直接上单页
- 安装并使用html-webpack-plugin(单页应用模板插件)
- 安装并使用style-loader css-loader(处理css的loader)
- webpack进行打包
案例三:使用less以及添加兼容性语法
- 安装并使用less less-loader(处理less语法)
- 安装并使用、 postcss-loader postcss-cssnext autoprefixer postcss-import cssnano(处理样式前缀)以及添加postcss.config.js配合使用添加浏览器前缀,兼容不同浏览器
- webpack进行打包
案例四:css分离以及图片打包
- 安装并使用url-loader file-loader(处理图片压缩、base64编码)
- 安装并使用 mini-css-extract-plugin(分离css)
- webpack进行打包
案例五:热更新
HMR即Hot Module Replacement是指当你对代码修改并保存后,webpack将会对代码进行重新打包,并将改动的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,去实现局部更新页面而非整体刷新页面
- 为静态文件提供服务
- 自动刷新和热替换(HMR)
webpack打包和webpack-dev-server热更新
webpack | webpack-dev-server |
---|---|
适用于webpack打包机制 | 适用于webpack打包机制 |
输出真实的文件 | 输出的文件只存在于内存中,不输出真实的文件 |
文件修改要重新打包 | 文件修改保存会自动更新 |
按生产环境需求进行打包生产环境代码 | 按开发环境需求进行打包开发环境代码 |
常用配置
- port:端口号
- host:域名
- historyApiFallback:配置属性是用来应对返回404页面时定向到特定页面用
- compress:设置为true的时候对所有的服务器资源采用gzip压缩
- hot:DevServer默认的行为是在发现源代码被更新后会通过自动刷新整个页面来做到实现预览,开启模块热替换功能后在不刷新整个页面的情况下通过用新模块替换老模块来实现实时预览
- inline: 将webpack-dev-server客户端加入到webpack入口文件的配置中
- contentBase:是指定在哪个路径下或者文件夹下中开启服务器;
第四课之webpack进阶
课程内容
基础webpack的完善
基本的webpack还缺少js的编译语法,以及区分开发环境和生产环境
- 开发环境通常指的是我们正在开发的这个阶段所需要的一些环境配置,也就是方便我们开发人员调试开发的一种环境
- 生产环境通常指的是我们将程序开发完成经过测试之后无明显异常准备发布上线的环境,也可以理解为用户可以正常使用的就是生产环境
案例
案例一
babel:Babel 是一个 JavaScript 编译器,可以把ES6的语法转为兼容浏览器的ES5语法
- 安装并使用babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/runtime(高级js语法转换)
- 项目根目录添加.babelrc文件(文件需要的配置项主要有预设(presets)和插件(plugins))
- webpack进行打包
案例二
开发环境和生产环境
开发环境和生产环境有很多通用的代码,可以将通用的代码提取出来,例如loaders,output等,所以可以形成三个文件,webpack.prod.config.js、webpack.dev.config.js、webpack.common.config.js,分离出来如何将他们分别合并,这时候就需要使用到webpack-merge,进行配置合并
- 安装webpack-merge(合并webpack的一个库)
- 新建webpack.common.config.js、webpack.dev.config、webpack.prod.config.js
- 开发和生产环境需要引入webpack-merge以及合并配置
var { merge } = require('webpack-merge');
var commonConfig = require('./webpack.common.config');
// webpack开发环境合并
module.exports = merge(commonConfig, devConfig);
// webpack生产环境合并
module.exports = merge(commonConfig, prodConfig);
- 配置package.json脚本命令,分别读取webpack的文件
"start": "cross-env NODE_ENV=development ./node_modules/.bin/webpack-dev-server --config webpack.dev.config.js",
"release": "cross-env NODE_ENV=production ./node_modules/.bin/webpack --config webpack.prod.config.js"
高级配置项
- 定义环境变量 cross-env
能够提供一个设置环境变量的scripts,让你能够以unix方式设置环境变量,然后在windows上也能兼容运行
npm install --save-dev cross-env
- resolve 配置
resolve 配置 webpack 如何寻找模块所对应的文件。webpack 内置 JavaScript 模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你可以根据自己的需要修改默认的规则
alias:给你的项目常用的引用路径给别名
resolve: {
alias: {
'@css': '../less'
}
}
- extensions设置查找文件顺序
适配多端的项目中,可能会出现 .web.js, .wx.js,例如在转web的项目中,我们希望首先找 .web.js,如果没有,再找 .js,默认找.js
- externals
如果需要引用一个库,但是又不想让webpack打包(减少打包的时间),并且又不影响以import方式引用,那就可以通过配置externals
日常实用的插件
- copy-webpack-plugin:在webpack中拷贝文件和文件夹,configs文件是如何读取的
- DefinePlugin:通过webpack定义全局变量,例如有些功能我希望开发环境执行这个功能,而生产环境执行另外的功能
- CleanPlugin: 清除文件夹,一般用于清除dist文件夹,保证每次打包dist文件夹目录是干净的
打包优化
webpack-bundle-analyzer
可以直观分析打包出的文件包含哪些,大小占比如何,模块包含关系,依赖项,文件是否重复,压缩后大小如何,针对这些,我们可以进行文件分割等操作
1.代码压缩以及公用模块处理
核心配置optimization、splitChunks、cacheGroups、runtimeChunk、minimizer
2.happypack提升打包速度
webpack 是单线程的,就算此刻存在多个任务,你也只能排队一个接一个地等待处理。这是 webpack 的缺点,好在我们的 CPU 是多核的,Happypack 会充分释放 CPU 在多核并发方面的优势,帮我们把任务分解给多个子进程去并发执行,大大提升打包效率
3:dll动态链接库
当有依赖包较大时,依赖包不常改动,可以将这些依赖包通过DllPlugin打包,这样开发和生产环境构建时就可以不构建这些文件,提高打包编译速度
4:引用cdn
- cdn优化是指把第三方库比如(vue,vue-router,axios)通过cdn的方式引入项目中,这样vendor.js会显著减少,并且大大提升项目的首页加载速度
- css、image都可以放到cdn提升页面加载速度
- 第一步:引用antd、react、react-dom、moment、lodash的cdn
- 第二步:webpack.config.common.js的配置项externals修改
externals: {
"react": "React",
"react-dom": "ReactDOM",
"lodash": "_",
"moment": "moment",
}
5.gzip
开启gzip压缩可以有效压缩资源体积,压缩比率在3到10倍左右,可以大大节省服务器的网络带宽,提高资源获取的速度,节省流量,根据gzip使用的算法特性,代码相似率越大压缩效率越高
-
安装并使用compression-webpack-plugin进行压缩
-
dll更用于提升打包速度
-
cdn更用于提升页面加载速度
-
压缩项目资源文件
总结
第五课之初步认识react
课程内容
- 1.课程回顾
- 2.react介绍
- 3.react安装
- 4.生命周期、事件、数据流讲解
- 5.父子组件传值
1.什么是React
React是Facebook推出的一个JavaScript库,它的口号就是“用来创建用户界面的JavaScript库”,所以它只是和用户界面打交道,可以把它看成MVC中的V(视图)层。
为什么使用react
- 虚拟dom效率性能高,不需要操作dom(频繁操作dom很消耗性能)
- 使用组件化开发方式,高复用、维护容易,逻辑清晰
- 技术成熟,社区完善,插件齐全,适用于大型Web项目(生态系统健全),由Facebook专门的团队维护,技术支持可靠,是现在最主流的技术之一
- 全局单向数据流,数据流向清晰,大型项目不在话下
组件化
- 每一个ReactJS文件都是一个组件,含视图、逻辑操作、数据
- 复用、封装性强
单向数据流
- 子组件对于父组件传递过来的数据是【只读】的,不能直接更新父组件数据、这样组件更加简单易把握、只会在父组件修改数据,追查问题的时候可以跟快捷
- 顶层组件某个props改变会递归遍历整棵组件树,重新渲染使用这个属性的插件
虚拟dom
- React的设计中,开发者基本上无需操纵实际的DOM节点,每个React组件都是用Virtual DOM渲染的。
- 用 JavaScript 对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树
- 新的虚拟 DOM 与原来的虚拟 DOM进行比对时,它会进行同层比较,即相同的节点层进行比较,如果不同则直接将原始虚拟 DOM 中该节点层及以下的节点全部删除,重新生成新的虚拟 DOM 节点,而不会继续向下比对
jsx
- JSX 是 JavaScript 语言的一种语法扩展,长得像 HTML,但并不是 HTML。
- React.js 可以用 JSX 来描述你的组件长什么样的。
- JSX 在编译的时候会变成相应的 JavaScript 对象描述。
- react-dom 负责把这个用来描述 UI 信息的 JavaScript 对象变成 DOM 元素,并且渲染到页面上。
key
- 例如:在遍历数据时,推荐在组件中使用 key 属性
- 循环需要加key:你在 JSX 模板中遍历 state 中某个数据时,为什么不加 key 值浏览器会报警告,这是因为你不再遍历的每条数据加上 key 值,更改 state 中那条数据的值,生成虚拟 DOM 后,React 就不知道原始遍历的数据和这次更新后遍历的数据一一对应的关系,就会再次重新渲染,而加上 key 值,它则能迅速比对出有差异的部分进行部分的更新。
- 为什么不建议用 index 作为 key 值:因为当你插入、 删除中间的数据时,从改变的那个数据开始,后续每个数据的 index 值就会变,从而就导致了每个数据的 key 值相应变化了,这样依旧会引起大规模渲染,这就是其中的原因
虚拟dom、jsx
- 从 JSX 到页面到底经过了什么样的过程:
生命周期
- 挂载卸载过程
- constructor():React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。
- componentWillMount():它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时
- componentDidMount():组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
- componentWillUnmount ():在此处完成组件的卸载和数据的销毁。(移除计时器)
- 更新过程
- componentWillReceiveProps (nextProps):接受一个参数nextProps,通过对比nextProps和this.props是否改变,判断要做的操作,只要父组件改变都会造成子组件componentWillReceiveProps接收,也会增加组件的重绘次数,浪费性能,写法不好容易死循环
- shouldComponentUpdate(nextProps,nextState):主要用于性能优化(阻止组件渲染),因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
- componentWillUpdate (nextProps,nextState):shouldComponentUpdate返回true以后,组件进入重新渲染的流程,进入componentWillUpdate,这里同样可以拿到nextProps和nextState。
- componentDidUpdate(prevProps,prevState):react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state
- render():render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染
- React新增的生命周期
- getDerivedStateFromProps(nextProps, prevState):代替componentWillReceiveProps(),获取props并修改state
- getSnapshotBeforeUpdate(prevProps, prevState):一般的用法就是获取更新前的DOM
- React17版本废弃的生命周期
- componentWillMount
- componentWillReceiveProps(可以用getDerivedStateFromProps和componentDidUpdate搭配使用替代)
- componentWillUpdate
render(不要在render里setState)
- 一个组件类必须要实现一个 render 方法。
- 这个 render 方法必须要返回一个 JSX 元素。
- 必须要用一个外层的 JSX 元素把所有内容包裹起来。
事件
绑定事件,this指向会改变所以我们通过代码控制不改变this的指向
- 写法一:构造函数里绑定事件,效率高
- 写法二:使用bind this
- 写法三:方法使用箭头函数,this指向不会改变,简单易用,效率高,建议使用
- 写法四:在标签上调用函数,每次渲染会调用,不建议使用
数据流讲解
1.state:内部定义,它只是用来控制这个组件本身自己的状态,页面渲染通过setState进行完成。
2.setState:
- 当我们调用这个函数的时候,React.js 会更新组件的状态 state ,并且重新调用 render 方法,然后再把 render 方法所渲染的最新的内容显示到页面上
- React.js为了批次与效能并不会马上修改state。而是把这个对象放到一个更新队列里面,稍后才会从队列当中把新的状态提取出来合并到 state 当中,然后再触发组件更新。
想要即时获得改变后的state
- 使用回调函数
this.setState({ val: this.state.val + 1 }, () => {
console.log(this.state.val);
})
3.props:外部传入,包括父子组件之间的通信,全局数据流的传递
- 父子组件之间的通信,子组件无法直接修改父组件的props,通过this.props接收
- 全局数据流的传递(dva)
案例
父子组件传值
- 父组件使用子组件并添加属性进行传值
- 子组件接收属性或者方法
- 实现父组件state的使用,以及子组件props的使用
ref
react提供的一个特殊的属性ref,可以绑定到render()输出的任何组件上,这个特殊的属性允许你引用 render() 返回的相应的dom节点
- 父组件获取子组件的数据
- 获取组件节点
第六课之antd以及组件开发介绍
ant design的介绍
antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。
特性
- 提炼自企业级中后台产品的交互语言和视觉风格。
- 开箱即用的高质量 React 组件。
- 使用 TypeScript 开发,提供完整的类型定义文件。
- 全链路开发和设计工具体系。
- 数十个国际化语言支持。
- 深入每个细节的主题定制能力。
文档
目前我们项目只能使用ant design3.x.x版本点击这里,现在最新的4版本在我们项目里不能使用
antd开发页面方式的介绍
-
第一步:打开antd的官方文档3.x.x版本
-
第二步:查找想要使用的组件
-
第三步:查看代码演示,看哪个代码演示的地方是符合我们原型图需要的组件
-
第四步:查看API(最下面)和使用方式以及复制功能组件的代码,粘贴到项目里,修改数据源以及事件进行开发
后续就进行项目里组件的复制粘贴就可以进行开发 -
组件使用方式
-
流程如图
组件开发步骤
- 定义组件
- 添加construcor
- 添加生命周期
- 添加事件
- 编写render里面的代码
- 面包屑组件
- 查询组件
- 操作栏
- 表格
- 弹窗
- 新增编辑页
组件功能图
常用的antd组件
让大家知道有这个组件,了解之后针对原型的特殊组件可以知道到底有没有
通用
- Button按钮
- Icon图标
布局
Grid组件
- Row行组件
- Col列组件
导航
- Steps进度条
数据录入
- CheckBox多选框
- DatePicker日期选择框
- Form表单
- InputNumber数字输入框
- Radio单选框
- Switch开关
- Select选择器
- TimePicker时间选择框
- Upload上传
数据展示
- Popover气泡卡片
- Tooltip文字提示
- Tabs标签页
- Table表格
- Modal对话框
- Message全局提示
- Popconfirm气泡确认框
- Spin加载中
其他
- Divider分割线
案例
实现列表的增删查改
第一步:查看ant文档并添加Form组件
第二步:查看ant文档并添加table组件
第三步:添加中间区域的Button组件用于新增
第四步:父组件添加新增组件弹窗ShowModal
第五步:添加子组件ShowModal
第六步:实现新增功能(push)
第七步:实现删除功能(findIndex,splice)
第八步:实现修改功能(findIndex)
第九步:实现查询功能(filter)
第七课之dva以及前后端交互
dva介绍
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
数据流向
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)
如图:
react数据流
- 1.react页面渲染都是通过修改state进行页面渲染的,而state只能控制当前页面
- 2.使用react代码开发,要抛弃之前ajax的理念(通过数据以及操作dom进行页面调整),使用state、props数据流进行页面开发,如果直接操作dom,不更新state在没玩明白数据流的情况下可能会出问题,需要时间去积累经验
- 3.这时候当前组件想要操作别的组件的state尤为困难,这时候就需要全局数据流方案redux
简单来说dva可以解决react数据流跨多级组件的问题,数据操作更方便
dva配置项
const app = dva({
history, // 指定给路由用的 history,默认是 hashHistory
initialState, // 指定初始数据,优先级高于 model 中的 state
onError, // effect 执行错误或 subscription 通过 done 主动抛错时触发,可用于管理全局出错状态。
onAction, // 在 action 被 dispatch 时触发
onStateChange, // state 改变时触发,可用于同步 state 到 localStorage,服务器端等
onReducer, // 封装 reducer 执行。比如借助 redux-undo 实现 redo/undo
onEffect, // 封装 effect
onHmr, // 热替换相关
extraReducers, // 指定额外的 reducer,比如 redux-form 需要指定额外的 form reducer
extraEnhancers, // 指定额外的 StoreEnhancer ,比如结合 redux-persist 的使用
});
Models
如果对于Redux的action,reducer写法很熟悉的同学一定意识到,以往(即使是Redux的官方例子中)会在src目录下,新建reducers以及actions目录用于整体存放reducer和action,针对不同的功能模块还需要分别在这两个目录下设置子目录或子文件区分。
但dva的model完美的让某一个模块的状态池(对应某一个模块),action和reducer同时在一个js文件中维护,这样整个应用的目录结构会清晰和简洁很多,但也因此在dva项目中发起的任何一个action,type中必须包含对应model的命名空间(namespace)
import * as addbanner from '../services/addbanner';
export default {
namespace: 'addbanner', // 命名空间
state: {},
subscriptions: { // 监听
setup({ history, dispatch }, onError) {
}
},
effects: {
//call:执行异步函数
//put:发出一个 Action,更新store,类似于 dispatch
*bannerlist(action, { call, put }) {
const testRes = yield call(addbanner.bannerlist, action.params);
yield put({
type: 'test',
payload: {
bannerlistRes: testRes
},
});
return testRes;
},
reducers: { // 接收数据并改变state,然后进行返回,命名要和effects里的函数type一致
test(state, { payload }) {
return {
...state,
...payload,
};
}
},
};
dva开发流程
- 流程图
课堂案例
实现列表的增删查改(一级分类)
功能点、交互步骤
查询:点击查询获取查询数据->调用search查询组装数据->调用getData公用函数->获取返回数据->修改state,渲染页面
新增编辑:点击父组件操作功能,调用弹窗->表单数据完成,并按后端要求组装好数据,提交新增、编辑->获取返回数据->调用查询,关闭弹窗
删除:点击删除->确认删除交互->获取返回数据->调用查询
函数讲解
- getData:查询公用函数
- search:处理表单values并调用,查询
- sureAdd:子组件弹窗,新增编辑一级分类代码
- batchDeleteInfo、deleteInfo:删除和批量删除
父子组件传值
{showModal && <ShowModal hideFildModel={this.showModal} getData={this.getData} rowInfo={rowInfo} />}
dva的使用
// 去除子组件的callback获取返回值的方法
this.props.dispatch({
type: 'primary/saveOneProClass',
payload: param,
// callback: ({ code, message: info }) => {
// if (code === '0') {
// this.props.getData();
// this.handleClose();
// } else {
// message.error(info);
// }
// }
});
// 父组件通过result接收数据进行比较,做修改
componentWillReceiveProps(nextProps) {
const { saveOneProClassResult } = nextProps.primary;
if (saveOneProClassResult !== this.props.primary.saveOneProClassResult) {
const { code, data } = saveOneProClassResult;
if (code === '0') {
this.getData();
this.showModal('showModal', false);
} else {
message.error(saveOneProClassResult.message);
}
}
}
第八课之提高开发效率的es6以及函数
es6的介绍
2011 年,ECMAScript 5.1 版发布后(es5),就开始制定 6.0 版了,ECMAScript 2015(简称 ES2015),ES6 的第一个版本,在 2015 年 6 月发布了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本,涵盖了 ES2015、ES2016、ES2017 等等
ES6中包含了许多新的语言特性,它们将使JS变得更加强大,更富表现力。对于很多开发者来说,ES6带来的新功能涵盖面很广,还有很多很便利的功能等(如:箭头的功能和简单的字符串插值),确实令人兴奋。
es6常用语法
let
ES6新增了let命令,用来生命变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
const
const声明一个只读的常量,一旦声明,常量的值就不能改变。
解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
- 对象解构赋值
// 1.获取键值
let obj ={ name:"abc",age:18 };
//用解构赋值的方式获取name、age
let { name, age } = obj; //创建了一个变量name,值=obj.name
// 2.重命名(比如想要获取数据的变量和我们已有的变量重名,就需要进行重命名)
let { name: n, age: a } = obj;
console.log(n,a); //"abc" 18
// 3.解构再解构
let jsonData = {
id: 42,
status: "OK",
data: {list:[],total:1000}
};
let { id, status, data: {list,total} } = jsonData;
- 数组解构赋值
// 获取对应索引下的值
let data= [1,2,3];
// 现在的写法,如果对不上,值就是undefined
let [a, b, c] = [1, 2, 3];
console.log(a,b,c);
- 字符串解构赋值
const [a, b, c, d, e] = 'hello';
console.log(a) // "h"
console.log(b) // "e"
- 函数解构赋值
function f1({ age,height }){
console.log(age);
console.log(height)
}
f1({age:5,height:180})
- 函数数组解构赋值
function f2([ a,b ]){
console.log(a);
console.log(b)
}
f2([1,2])
- 默认值
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x, y = 'b'] = ['a', null]; // x='a', y=null
// 注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效,null和0均不生效
模板字符串
// 用法:使用``包裹字符串,变量使用${}
let s1 = 3, s2 = 4;
let s4 = ` a ${s1 === 1 ? '1' : '2'} b ${s2}`;
替换
// 只能替换第一个
'aabbcc'.replace('b', '_')
// 使用正则进行替换所有
'aabbcc'.replace(/b/g, '_')
// 现在可以使用replaceAll来替换所有
'aabbcc'.replaceAll('b', '_')
箭头函数
ES6 允许使用“箭头”(=>)定义函数。
使用注意点
箭头函数有几个使用注意点。
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
let getTempItem = id => ({ id: id, name: "Temp" });
arr.map(x => (x.id = x.id * x.id));
扩展运算符
扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
// 1.修改对象数据
let obj = { pageIndex: 1, pageSize: 10 }
let values = { pageSize: 20 };
// 现在的写法
let payLoad = { ...obj, ...values };
console.log(payLoad);
// 2.合并数组
// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1 = [...arr1, ...arr2];
console.log(arr1);
// 3.与解构结合,并且扩展运算符只能放在最后
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first) // 1
console.log(rest) // [2, 3, 4, 5]
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x) // 1
console.log(y) // 2
console.log(z) // { a: 3, b: 4 }
// 4. 克隆合并对象
let a = { name: '123', age: 456 };
let aClone = { ...a };
console.log(aClone);
// 等同于
// let aClone = Object.assign({}, a);
// console.log(aClone);
let b = { name: '1233', age: 4567 };
// let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
console.log(ab);
数组的扩展
// 1. find:用于找出第一个符合条件的数组成员。
let arr = [1, 4, -5, 10].find((n) => n < 0)
// 2.findIndex:返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
let arr = [1, 5, 10, 15].findIndex(function (value, index, arr) {
return value > 9;
})
// 3.includes:判断元素是否存在 返回true/false
let arr = [1, 5, 10, 15].includes(5);
// 4.filter:按条件进行数组过滤
var arr = [20, 30, 50, 96, 50]
var newArr = arr.filter(item => item > 40)
// 5.every:判断数组中是否每个age都大于22
let arr = [{name: 'Tom',age: 22},{name: 'Sun',age: 23},{name: 'Mack',age: 25,sex: '男'}];
let newArr = arr.every(item => item.age > 22)
// 6.some:判断数组中是否某个age都大于22
let newArr = arr.some(item => item.age > 22);
// 7.reduce:处理数组,适用于求和、求乘积
var arr = [1, 2, 3, 4];
var sum = arr.reduce((x,y)=>x+y)
var mul = arr.reduce((x,y)=>x*y)
console.log( sum ); //求和,10
console.log( mul ); //求乘积,24
// 8.map:方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
let data = [1, 2, 3, 4, 5]
let nowData = data.map(item => (item = 2));
对象的扩展
// 1.对象赋值
const foo = 'bar';
const baz = {foo}; // {foo: "bar"}
// 2.数组key值使用变量
let lastWord = 'last word';
const a = {
[lastWord]: 'world'
};
a[lastWord] // "world"
Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大
Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
Async
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
import export
ES6模块功能主要有两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
- export
// 1. 直接输出变量(方法、类)
export var m = 1;
export function multiply(x, y) {
return x * y;
};
// 2. 使用大括号指定所要输出的一组变量(方法、类)
var m = 1;
export { m };
// 3. as关键字重命名
// 重命名后,v2可以用不同的名字输出两次
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
- import
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块对外接口的名称相同。
// 1. 引入变量(方法、类)-(逐一加载),引入的名字要和导出的名字相同
import { firstName, lastName, year } from './profile.js';
// 2. as关键字重命名
import { lastName as surname } from './profile.js';
// 3. 整体加载
import * as circle from './circle';
- export default
export default就是输出一个叫做default的变量或方法,系统允许自定义命名
// 默认输出一个函数
function getName() {
console.log('foo');
}
export default getName;
// 引用并指定名字(可以按别的名字命名)
import customName from './export-default';
前端调试
前端js调试是前端开发学习最为关键的一个环节,熟练地代码调试可以大大减少问题查找时间
- 使用console.log()
- 使用alert()
- 可在任意想要调试的地方加上debugger(就是我们常说的打断点)
- 谷歌浏览器的source里面打断点,ctrl+p 查找对应的js,找到想要调试的代码打断点
F8 相当于后端的 F5
F10 F11和后端调试功能类似(F11很少用)
js常用功能
操作数组
/* 操作数组的常用方法: */
var arr = [10, 20];
/* [1] 在数组的后面添加元素 arr.push(ele1,ele2...) */
arr.push(30);
/* [2] 在数组的前面添加元素 arr.unshift(ele1,ele2...)*/
var arr = [10, 20];
arr.unshift(3);
/* [3] 删除数组中最后一个元素 */
var arr = [10, 20];
arr.pop();
/* [4] 删除数组中第一个元素 */
var arr = [10, 20];
arr.shift();
/* [5] 合并数组的方法 */
/* 语法:arr.concat(ele1|arr,ele2...) */
var arr1 = ["熊大", "熊二"];
var arr2 = ["佩琪", "乔治"];
var arr3 = arr1.concat(arr2);
/* [6] 截取数组的方法(不会改变原来的数据) */
/* 语法:arr.slice(startIndex,endIndex) */
/* 注意: */
/* (1) 该方法可以只传递一个参数,表示从这个位置开始截取直到数组的末尾 */
/* (2) 如果没有传递任何参数,那么表示截取整个数组 */
var data = ["熊大", "熊二", "佩琪", "乔治", 100, 200, "毛毛", "吉吉"];
var res1 = data.slice(1, 4);
var res2 = data.slice(1);
/* [7] 删除并插入数组的方法(会改变原来的数据) */
/* 语法:arr.splice(start,deleteCount.val1,val2,...):从start开始删除deleteCount项 */
/* 注意: */
/* (1) 该方法可以只传递一个参数,表示从这个位置开始截取直到数组的末尾 */
/* (2) 如果没有传递任何参数,那么表示截取整个数组 */
var data = ["熊大", "熊二", "佩琪", "乔治", 100, 200, "毛毛", "吉吉"];
data.splice(1, 1, '张三'); // ["熊大", "张三", "佩琪", "乔治", 100, 200, "毛毛", "吉吉"]
/* [8] 将数组反序 */
/* 语法:arr.reverse(): */
var a = [1, 2, 3, 4, 5]
var b = a.reverse();
/* [9] 添加分割符并变成字符串 */
/* 语法:arr.join('|'): */
var a = [1, 2, 3, 4, 5]
var b = a.join("|");
/* [10] 字符串按照字符切割成数组 */
// 2.数组key值使用变量
let lastWord = 'last,word,hello';
let arr = lastWord.split(',');
/* [11] 数组元素索引并返回元素索引,不存在返回-1,索引从0开始 */
/* 语法:arr.indexOf('a'): */
var a = ['a', 'b', 'c', 'd', 'e'];
console.log(a.indexOf('a'));//0
/* [12] 数组元素排序 */
/* 语法:arr.sort(): */
var arr = [{ age: 1 }, { age: 5 }, { age: 3 }, { age: 2 },];
arr.sort((num1, num2) => num1.age - num2.age);
实用的代码
// 1.判断值是否存在(a为null、undefined、0都隐性转换为false),常用在判断值存不存在,数组长度是不是0
var a = null, b = 1;
if (a) {
console.log('有a');
}
if (b) {
console.log('有b');
}
// 2.获取变量给默认值(0 null undefined false '')
var a = '';
let b = a || '默认值';
let param = { prop: 1 };
//let param=null;
let variable = param && param.prop;
// 3.if判断简写
let a = 1, b = 3, c = 5, d = 7;
a === 1 && (b = 2);
b = a ? c : d
// 4.获取状态,针对没有规律的键值对,可以用对象进行保存
// 代替if判断或者switch case
// let a = 'success';
// let b = '';
// switch (a) {
// case 'success':
// b = '成功';
// break;
// case 'fail':
// b = '失败';
// break
// case 'wait':
// b = '待充值'
// break
// }
// console.log(b);
// const obj = { 'success': '成功', 'fail': '失败', 'wait': '待充值' };
// console.log(obj.success);
// 针对按照索引来的键值对,可以用数组进行保存
let obj = ['', '成功', '失败', '代充值'];
console.log(obj[1]);
// 5.获取浏览器缓存
localStorage.setItem('item', 1);
console.log(localStorage.getItem('item') === localStorage.item);
localStorage.setItem('aaa', JSON.stringify({ a: 1 }));
console.log(JSON.parse(localStorage.aaa));
// 6.移动端调试利器
<script src="https://cdn.bootcss.com/vConsole/3.3.4/vconsole.min.js"></script>
<script>
// 初始化
var vConsole = new VConsole();
console.log('Hello world');
</script>
第九课之从零开始页面开发
页面搭建步骤
- 1.使用脚手架生成商户侧项目,并安装依赖(rdms有自己的脚手架生成)
- 2.使用vscode插件或者代码片段生成静态页面并注册路由
- 3.修改组件使用配置以及添加代码
- 4.使用dva进行异步交互的页面开发,并和组件连接
脚手架生成项目
使用vscode插件或者代码片段生成静态页面并注册路由
页面开发流程
- 1.通过vscode插件生成页面组件、models、services
- 2.路由注册对应页面
- 3.index.js引用对应的页面models
- 4.models,services进行细微调整
- 5.api接口地址调整对应后端接口地址
1.vscode插件使用方式
- 1.找到vscode插件create-react-component-song并安装
- 2.src目录下右键使用Create-Class-Component创建对应页面,输入页面名称,直接生成对应的页面以及models和services
2.路由注册
// 定义组件
const CouponsList = dynamic({
app,
component: () => import('./components/CouponList'),
});
// 配置路由(路由需要在商户控制台配置才能使用,否则会报无权限访问)
<Route exact path="/marketingManage/couponsList" render={(props) => WraperRouter(props, CouponsList)} />
3.index.js注册页面的models
import { default as couponList } from './models/couponList';
app.model(couponList);
修改组件使用配置以及添加代码
- 查询组件配置searchConfig
- 表格列数据columns
- 查询、删除、启用禁用事件的异步交互方法名修改(dispatch交互)
- 新增、编辑按钮交互修改
4. 修改异步交互
models
// 原本的获取列表的函数
*couponListList({ payload, callback }, { call, put }) {
const testRes = yield call(couponList.couponListList, payload);
yield put({
type: 'success',
payload: {
couponListListResult: testRes
}
});
callback && callback(testRes);
return testRes;
},
// 修改为以下,主要修改的地方有三处
*getMerCouponInfoPage({ payload, callback }, { call, put }) {
const testRes = yield call(couponsList.getMerCouponInfoPage, payload);
yield put({
type: 'success',
payload: {
getMerCouponInfoPageResult: testRes
}
});
callback && callback(testRes);
return testRes;
},
修改services
// 原本的获取列表的函数
export function couponListList(params) {
return axios.get(configs.host.test + Api.couponList, { 'params': params });
}
// 修改为以下,主要修改的地方有两处,提交参数格式与后端沟通进行交互,通常get delete的参数使用{params},post,put参数使用params
export function getMerCouponInfoPage(params) {
return axios.get(configs.host.test + Api.getMerCouponInfoPage, { 'params': params });
}
... 其他的类似
修改api.js添加接口地址
getMerCouponInfoPage: '/api/MerCouponActivity/GetMerCouponInfoPage',
- 以上操作已经初步实现了页面的查询、删除、禁用功能
实现编辑、新增功能
新建CoupomsListAdd.js并注册路由
- 1.获取id,定义详情数据以及页面需要定义的state等
- 2.时间转换,文本框输入以及切换控制详情页展示
- 3.选择弹窗进行页面加载
- 4.新增编辑的数据处理
// 1. 详情页加载逻辑
constructor(props){
const id = mathmanage.getParam('id');
this.state = {
detailData: {
baseProInfo: [],
},
oneClassList: [],
id,
type,
couponDataSource: [],
showRelationCouponModal: false,
};
}
componentDidMount() {
const { id } = this.state;
if (id) {
//请求商品详情
this.props.dispatch({ type: 'couponsList/getMerCouponInfoDetails', payload: { id } });
}
}
// 2.修改事件
// 提交
btnOk = () => {
const { detailData, id, type } = this.state;
this.props.form.validateFields((err, values) => {
if (!err) {
// 优惠券展示
values.viewType = values.viewType ? 1 : '';
// 其他限制
values.limitRule = values.limitRule ? 1 : '';
values.startCouponTime = moment(values.couponTime[0]).format('YYYY/MM/DD HH:mm:ss');
values.endCouponTime = moment(values.couponTime[1]).format('YYYY/MM/DD HH:mm:ss');
if (!detailData.baseProInfo.length) {
return message.error('请关联优惠券');
}
if (values.totalNum > detailData.baseProInfo[0].totalNum) {
return message.error('发放总量不能大于关联的优惠券的发放总量');
}
// 如果是编辑并且不是复制
if (id && !type) {
return this.props.dispatch({ type: 'couponList/updateMerCouponActivity', payload: { ...detailData, ...values, id } });
}
this.props.dispatch({ type: 'couponList/addMerCouponActivity', payload: { ...detailData, ...values } });
}
});
}
// 选择弹窗之后的关闭事件
hideModal = (baseProInfo) => {
const { detailData } = this.state;
if (baseProInfo) {
detailData.baseProInfo = baseProInfo;
detailData.batchNumber = detailData.baseProInfo.length ? detailData.baseProInfo[0].batchNumber : '';
detailData.type = detailData.baseProInfo.length ? detailData.baseProInfo[0].type : '';
detailData.content = detailData.baseProInfo.length ? detailData.baseProInfo[0].content : '';
detailData.productId = '';
}
this.setState({
detailData,
showRelationCouponModal: false
})
}
// 文本改变事件操作detailData来进行控制
changeInput = (value, field) => {
const { detailData } = this.state;
detailData[field] = value;
if (field === 'noLimitNumber' && value) {
detailData.limitNum = null;
}
this.setState({
detailData
});
}
// 3.修改render数据
<div className="common-page-content couponslist-add">
<Spin spinning={!!this.props.loading.models.couponsList}>
<Form >
<PageHeader
className="site-page-header"
title="基本信息"
/>
<FormItem {...formItemLayout2} label="券来源">
{getFieldDecorator('relationProduct', {
initialValue: 'relationProduct',
rules: [
{ required: true, message: '请选择券来源', },
]
})(
<Fragment>
{detailData.baseProInfo.length ?
<Table
scroll={{ x: getScrollWidth(this.columns) }}
rowKey="batchNumber"
columns={this.columns}
pagination={false}
dataSource={detailData.baseProInfo} />
: <a onClick={this.relationCoupon}>选择优惠券</a>}
</Fragment>
)}
</FormItem>
{/*此处省略多行代码*/}
</Form>
</Spin>
{showRelationCouponModal && <CouponsModal hideModal={this.hideModal} baseProInfo={cloneDeep(detailData.baseProInfo)} />}
</div>
添加页面对应的models、services、api.js
处理接口交互的返回值(dva)
const { getMerCouponInfoDetailsResult, updateMerCouponActivityResult, addMerCouponActivityResult } = nextProps.couponList;
if (updateMerCouponActivityResult !== this.props.couponList.updateMerCouponActivityResult) {
if (updateMerCouponActivityResult.code === '0') {
message.success(updateMerCouponActivityResult.message);
return this.props.history.push('/marketingManage/couponsList');
}
return message.error(updateMerCouponActivityResult.message);
}
if (getMerCouponInfoDetailsResult !== this.props.couponList.getMerCouponInfoDetailsResult) {
if (getMerCouponInfoDetailsResult.code === '0') {
const { data } = getMerCouponInfoDetailsResult;
let batchNumber = data.batchNumber;
let batchName = data.batchName;
this.props.dispatch({
type: 'couponList/getCouponInfos', payload: {
pageIndex: 1, pageSize: 10, batchNumber, batchName
}
}).then((res) => {
const { list } = res.data;
if (res.code === '0') {
data.baseProInfo = list;
// 优惠券展示
data.viewType = data.viewType ? true : false;
// 其他限制
data.limitRule = data.limitRule ? true : false;
data.noLimitNumber = data.limitNum ? false : true;
return this.setState({
detailData: data
})
}
return message.error(res.data.message);
});
} else {
return message.error(getMerCouponInfoDetailsResult.message);
}
}
关于表格复选的问题
- 1.需要传递selectRows和selectRowKeys进行复选,selectRowKeys的id必须和表格的id为统一数值才能选中,selectRows主要用于多选保留之前选中的行(antd不会通过selectRowKeys自动记录你之前选中的行数据,只会记录当前选中的行数据,所以传递过来一起进行记录,这样数据就不会丢失)
- 2.表格需要添加key的唯一标识,否则复选功能或者列表展示功能会有问题
- 3.分页之后不会记录前一页勾选的数据,使用封装的checkRows函数
前端开发着重了解
- ant design的使用
- 福禄组件库的使用
- dva、异步交互的使用
- 页面逻辑开发