My Little World

learn and share


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于
My Little World

react-navigation

发表于 2019-06-02

react-navigation

安装

1
2
3
yarn add react-navigation
yarn add react-native-gesture-handler
react-native link react-native-gesture-handler //Link 所有的原生依赖

配置

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
APP.js
import React from "react";
import { View, Text } from "react-native";
import { createStackNavigator, createAppContainer } from "react-navigation";
class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Home Screen</Text>
</View> );
} }
const AppNavigator = createStackNavigator(
{
HomeComponent: { screen: HomeScreenComponent } //screen属性设置组件路由,HomeScreen只是一个组件
Home: HomeScreen,//直接配置页面路由
Details: DetailsScreen
},
{
initialRouteName: "Home"//指定默认路由
defaultNavigationOptions: { //指定全局默认标题样式
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
},
});
const AppContainer = createAppContainer(AppNavigator);
export default class App extends React.Component {
someEvent() {
// call navigate for AppNavigator here:
this.navigator &&
this.navigator.dispatch( //在 App 容器中使用 dispatch,可以使用 ref 来调用 dispatch 方法
NavigationActions.navigate({ routeName: someRouteName })
);
}
render() {
return <AppContainer
//每当导航器管理的 navigation state 发生变化时,都会调用该函数
onNavigationStateChange={handleNavigationChange}
uriPrefix="/app"//应用可能会处理的 URI 前缀, 在处理一用于提取传递给 route 的一个 深度链接时将会用到
ref={nav => {
this.navigator = nav;
}}
/>;
}
}

跳转

this.props.navigation.navigate(路由名,传参对象)//跳转其他页,如果跳转本页,没反应
this.props.navigation.push(路由名)//可以重新加载当前页
每次调用 ‘ push ‘ 时, 我们会向导航堆栈中添加新路由。
当你调用 ‘ navigate ‘ 时, 它首先尝试查找具有该名称的现有路由, 并且只有在堆栈上没有一个新路由时才会推送该路由。
读取页面组件中的参数的方法:
this.props.navigation.state.params
this.props.navigation.getParam(key值)

如果当前页面可以执行返回操作,则 stack navigator 会自动提供一个包含返回按钮的标题栏
如果导航堆栈中只有一个页面,则没有任何可返回的内容,因此也不存在返回键。
this.props.navigation.goBack() //手动返回上一页

如果处在堆栈深处,上面有多个页面,此时想要将上面所有的页面都销毁,并返回第一个页面。
在这种情况下,我们知道我们要回到’ Home ‘,所以我们可以使用’ navigate(‘Home’) ‘,而不是push
另一个选择是’ navigation.popToTop() ‘,它可以返回到堆栈中的第一个页面。

如何发现用户离开和回来的某页面?
一个包含 页面 A 和 B 的 StackNavigator ,当跳转到 A 时,componentDidMount 方法会被调用; 当跳转到 B 时,componentDidMount 方法也会被调用,但是 A 依然在堆栈中保持 被加载状态,他的 componentWillUnMount 也不会被调用。
当从 B 跳转到 A,B的 componentWillUnmount 方法会被调用,但是 A 的 componentDidMount方法不会被调用,应为此时 A 依然是被加载状态。
React Navigation 将事件发送到订阅了它们的页面组件: 有4个不同的事件可供订阅:willFocus、willBlur、didFocus 和 didBlur。

标题栏

使用 组件静态属性navigationOptions

内容配置

title属性
方式一:对象

1
2
3
static navigationOptions = {
title: 'Home',
};

方式二:返回对象的属性

1
2
3
4
5
static navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('otherParam', 'A Nested Details Screen'),
};
};

尝试在navigationOptions中使用this.props可能很诱人,但因为它是组件的静态属性,所以this不会指向一个组件的实例,因此没有 props 可用。 相反,如果我们将navigationOptions作为一个函数,那么React Navigation将会用包含{navigation,navigationOptions,screenProps}的对象调用它 – 在这种情况下,我们只用关心navigation,它是与传递给页面的this.props.navigation相同的对象

从已加载的页面组件本身更新当前页面的navigationOptions配置
this.props.navigation.setParams({otherParam: ‘Updated!’})

标题样式

headerStyle:一个应用于 header 的最外层 View 的 样式对象, 如果设置 backgroundColor ,他就是header 的颜色。对象
headerTintColor:返回按钮和标题都使用这个属性作为它们的颜色。字符串
headerTitleStyle:如果想为标题定制fontFamily,fontWeight和其他Text样式属性,我们可以用它来完成。对象

全局配置

配置路由时利用defaultNavigationOptions配置
当全局设置了默认样式,具体页面也设置了static navigationOptions,则优先使用具体页面

使用组件

headerTitle:配置一个组件,自定义标题的样式
headerRight:自定义右侧按钮
headerLeft:自定义左侧/返回按钮
headerBackImage:自定义返回按钮图片
注意在navigationOptions中this绑定的不是 HomeScreen 实例,所以你不能调用setState方法和其上的任何实例方法。 这一点非常重要,因为标题栏中的按钮与标题栏所属的页面进行交互是非常常见的。

My Little World

二级菜单实现

发表于 2019-04-12

背景

page A是一个管理页面,数据通过table 展示
page B是一个详情页面,通过page A 中的一条数据跳转链接进来
page B数据获取时的参数需要在page A跳转时带进来
page B 本来只是一个简单页面,现在要在page B中添加一列菜单,可以导航多个不同内容详情
即现在page A要跳转的是一个带菜单的页面,点击每个菜单跳转不同的页面

原理

1.vue-router配置时,children属性配置的子页面,在访问时,会在父页面的router-view标签部分进行填充
如果父页面没有router-view标签子页面内容就没地方展示
菜单就放在父页面,点击跳转子页面路由
2.因为页面跳转完成后,当前路由对象不会携带自己的children属性,给子路由配置title,父页面拿不到
所以父页面的菜单映射关系需要在其他地方配置,传递进来

解决

方案一

子菜单名和对应路由的映射,放在page A跳转配置中,page A 配置的点击跳转的页面是其中一个子路由

1
2
3
4
5
6
7
{title: '名称', value: 'name', display: true,
shadow: {path: 'Adetail_C', key: '',title:'AAAA',submenu:[{
path:'Adetail_C',name:'C'
},{
path:'Adetail_D',name:'D'
}]},
},

新增子菜单路由页面page A-detail-router,子菜单映射页面page C和page D

页面page A-detail-router中,可以通过this.$route对象拿到page A配置的菜单映射关系,从而渲染菜单
切换子页面时,需要增加把参数传递到子页面中的逻辑

page C和page D根据路由中传递过来的page A点击数据的信息进行数据请求和页面渲染

在项目路由配置文件中配置路由

1
2
3
4
5
6
7
8
{ path: 'A',  name: 'A', title: 'XX管理', meta: {keepAlive: true}, component: () => import('@/pageA') },
{ path: 'AdetailRouter', name: 'AdetailRouter', title: 'XX详情', meta: {keepAlive: true}, component: () => import('@/A-detail-router'),
children:[{
path:'/Adetail_C',name: 'Adetail_C',meta: {previousMenu: 'A'}, component: () => import('@/Adetail_C')
},{
path:'/Adetail_D',name: 'Adetail_D',meta: {previousMenu: 'A'},component: () => import('@/Adetail_D')
}]
},

children配置path时,以‘/’开头和不以‘/’开头区别:
以’/‘开头最终访问路径就是配置路径‘#/子path’,相当于子路由当一级路由
但不以‘/’开头最终的访问路径是‘#/父path/子path’,
这里使用‘/’开头是因为更外层菜单,在跳转page A同级路由时使用的是push({path:xxx})
如果使用不以‘/’开头的配置,跳转page A同级路由,路径就会变成‘#/父path/xxx’,即父路径下跳转
会因为没有相应配置路由找不到页面,所以使用‘/’开头,当作父路由跳转
其实在跳转page A同级路由时使用的是push({name:xxx}),就可以直接解决‘#/父path/xxx’问题,
跳转正确页面

缺点:
直接通过浏览器刷新子页面时,路由对象的params属性数据清空,
路由页面菜单映射没有数据支持,菜单没有内容
子页面路由因此也拿不到参数,无法请求数据
动作流向只有利用路由对象的meta属性的previousMenu跳转回page A

方案二

子菜单名和对应路由的映射放在单独json文件中,利用子菜单名字获取映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//submenu.json
{
Adetail:{
title:'AAAA'
submenu:[{
name:'Adetail_C',//路由里配置的name
label:'C',
show:true //之后根据路由参数query决定是否显示该菜单
},
{
name:'Adetail_ D',
label:'D',
show:true
}]
}
}

在路由页面page A-detail-router中根据访问的子菜单路由名获取二级菜单,这样解决刷新页面菜单数据无处获取的问题

使用动态路由传递参数id保证页面刷新时子页面params至少有一个属性可以依赖来获取数据
更改路由配置

1
2
3
4
5
6
7
8
{ path: 'A',  name: 'A', title: 'XX管理', meta: {keepAlive: true}, component: () => import('@/pageA') },
{ path: 'Adetail', name: 'Adetail', title: 'XX详情', meta: {keepAlive: true}, component: () => import('@/A-detail-router'),
children:[{
path:'C/:id',name: 'Adetail_C',meta: {previousMenu: 'A'}, component: () => import('@/Adetail_C')
},{
path:'D/:id',name: 'Adetail_D',meta: {previousMenu: 'A'},component: () => import('@/Adetail_D')
}]
},

‘:id’对应路由对象中params属性的id属性,所以访问时,一定要在跳转的路由中配置{params:{id:xxxxxx}}
更改路由页面page A-detail-router中跳转其他路由页面时,参数配置

1
2
3
4
5
6
7
8
gotoPage (val,index) {
this.active = index
this.$router.push({
name: val.name,
params:{id:this.$route.params.id}, //给其他子页面传递id
query:this.$route.query//其他自定义参数
})
},

这样访问子页面时路径就变成’#/Adetail/C/xxxxxxxxxxxxxxxx’
以上处理基本解决掉方案一问题

后续需要根据page A 数据的其他属性值决定菜单显示,所以在配置跳转时配置要传递的属性key
在跳转时,根据KEY获取值,组装成都对象,放在query中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//配置:
{title: '名称', value: 'dbname', display: true,link: {path: 'databaseDetail_outline', key: 'dbinstance_id',query:['dbtype.dbtype']}}
link属性即与跳转相关的配置,
path:跳转路由名
key:以数据哪个属性当id,字符串
query:需要传递的其他参数,字符串数组
//列表点击跳转
jump (item, val) {
//根据key获取值,key对应的可能不是子属性,而是孙属性,或者更深层次属性,valueFromExpression函数用于获取深层属性值
let id = valueFromExpression(item, val.link.key)
let router = {
name: val.link.path,
params: {id: id}
}
if(val.link.query){//根据key值组装传参对象
router.query = val.link.query.map(key=>{return {[key]:valueFromExpression(item, key)}}).reduce((result,item)=>{return Object.assign(result,item)},{})
}
this.$router.push(router)
},

因为打算将路由页面page A-detail-router作为公共页面,page A同级页面的其他页面也可以使用
所以将判断子菜单是否显示的逻辑放在跳转的那个子页面

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//page A-detail-router
<template>
<div class="subcontainer" v-if="submenuConfig">
<div class="submenu">
<!-- <div class="subTitle" @click="goBack()">{{submenuConfig.title}}</div> -->
<div class="subTitle" @click="goBack()"><i class="fa fa-chevron-left"></i></div>
<template v-for="(item,index) in submenuConfig.submenu">
<div v-if='item.show' @click="gotoPage(item,index)" :key="index" class="subbtn" :class="active===index?'subactive':''">
<a>{{item.label}}</a>
</div>
</template>
</div>
<div class="content">
<router-view></router-view>
</div>
</div>
</template>
<script>
import submenuText from '../../../common/submenu'
export default {
name: 'submenu_detail',
data() {
return {
submenuConfig:null,
active:0
}
},
mounted() {
let routeName = this.$route.name
this.submenuConfig = submenuText[routeName.split('_')[0]] //获取菜单配置
this.active = this.submenuConfig.submenu.findIndex((item)=>{return item.name === routeName})
},
methods: {
gotoPage (val,index) {
this.active = index
this.$router.push({
name: val.name,
params:{id:this.$route.params.id},
query:this.$route.query
})
},
goBack(){
this.$router.push({
name:this.$route.meta.previousMenu
})
}
},
components: {
}
}
</script>
<style lang="less" scoped>
.subcontainer{
display: flex;
flex-grow: row nowrap;
justify-content: space-between;
align-content:stretch;
margin: -20px;
.submenu{
display: flex;
flex-flow: column;
align-content: center;
width: 200px;
flex:none;
background-color: #EAEDF1;
height: 100%;
position: absolute;
.subTitle{
//内容为标题时样式
// font-weight: bold;
// text-indent: 20px;
// height: 70px;
// line-height: 70px;
// background: #D9DEE4;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
font-weight: bold;
height: 70px;
line-height: 70px;
background: #D9DEE4;
font-size: 20px;
color: #546478;
text-align: center
}
.subTitle:hover{
color: #0080FF;
}
.subbtn{
width: 100%;
font-size: 15px;
text-align: center;
padding: 10px 0px;
a{
color:#000;
}
}
.subbtn:hover{
background-color: #f8f8f8;
background: #f8f8f8;
}
.subactive{
background-color: #fff;
background: #fff;
}
}
.content{
flex:1;
padding: 20px;
margin-left: 200px;
}
}
</style>

//子菜单页面
mounted() {
if(this.$route.query['dbtype.dbtype'] != 'mysql'){
this.$parent.submenuConfig.submenu[1].show = false
}else{
this.$parent.submenuConfig.submenu[1].show = true
}
this.getDetail(this.$route.params.id)
},
My Little World

webpack应用

发表于 2019-03-10

基本配置

1
2
3
4
5
6
7
8
const path = require('path');
module.export = {
entry:'./main.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'./dist')
}
}

接入babel

配置.babelrc

需要额外配置.babelrc文件,该文件是一个JSON格式文件
plugins属性,告诉Babel要使用哪些插件,这些插件可以控制如何转换代码
babel-plugin-transform-runtime:减少冗余代码
babel-runtime:可以导入babel-plugin-transform-runtime依赖的辅助函数
以上二者需要配套使用
presents属性,告诉Babel要转换的源码使用了那些新的语法特性,其值是一组plugins数组,
每一项,
如果不需要配置参数则使用字符串,表示插件名;
如果需要配置参数则使用数组,数组第一项为插件名,第二项为配置项组成的对象;
可以分为三大类:
1.已经被写入ES标准的特性:ES2015,ES2016,ES2017,Env (包含当前所有ES标准里的新特性)
2.社区提出但还未写入ES标准的特性:stage0,stage1,stage2,stage3,stage4,分别表示被纳入es标准的进度
3.特定场景下的语法特性,和es标准没有关系,例如要支持JSX,则使用babel-preset-react

1
2
3
4
{
"plugins":[["transform-runtime",{"polyfill":false}]]
"presents":[["es2015",{"modules":false}],"stage-4","react"]
}

webpack配置

Babel是转换代码功能,所以要配置相应loader

1
2
3
4
5
6
modules:{
rules:[{
test:/\.js$/,
use:['babel-loader']
}]
}

安装依赖模块

1
2
3
npm i -D babel-core babel-loader //webpack配置相关依赖
npm i -D babel-preset-env
npm i -D babel-preset-* //配置文件中的相关依赖

使用TypeScript

配置 tsconfig.json文件

1
2
3
4
5
6
7
8
9
"compilerOptions":{
"module":"commonjs",//编译的代码采用的模块规范
"target":"es5",//编译出的代码采用es哪个版本
"sourceMap":true,//输出Source Map 以方便调试
"importHelpers":true//禁止辅助函数重复出现在多个文件
},
"exclude":[
"node_modules"
]

webpack配置

使用loader做转换;修改文件查找规则

1
2
3
4
5
6
7
8
9
resolve:{
extension:['.ts','.js']
}
modules:{
rules:[{
test:/\.ts$/,
loader:'awesome-typescript-loader'
}]
}

安装依赖模块

1
npm i -D typescript awesome-typescript-loader

使用Flow检查器

使用语法

1
2
3
4
//@flow /*告诉检查器这个文件需要被检查*/
function squarel(n:number):number{
return n*n
}

使用配置

1.全局使用
npm i -g flow-bin 将可执行文件安装在全局,直接在项目根目录下使用flow命令执行代码检查
2.局部使用
npm i -D flow-bin 在项目中生成可执行文件,仅在某个项目中使用,
在package.json中配置使用命令

1
2
3
"script":{
"flow":"flow"
}

使用 npm run flow 执行代码检查

去除静态语法

采用flow静态语法的js无法在引擎中运行,所要去掉
1.使用flow-remove-types
2.一般使用flow的项目会使用es6,所以集成到babel配置中,
安装依赖模块:npm i -D babel-preset-flow
修改.babelrc,在preset中加入’flow’:”presets”:[…[],”flow”]

使用SCSS

SCSS又叫做SASS,可以使用一定语法编写css样式
转换方式
1.全局单文件转换
全局安装node-sass:npm i -g node-sass
执行命令:node-sass main.scss main.css //main.css即main.scss编译后的css文件

2.使用sass-loader

webpack配置

1
2
3
4
5
6
modules:{
rules:[{
test:/\.scss$/,
use:['style-loader','css-loader','sass-loader']
}]
}

安装依赖模块

npm i -D style-loader css-loader sass-loader //webpack loader
npm i -D node-sass //sass-loader 依赖node-sass

具体处理流程

sass-loader将scss源码转换为css,再将css代码交给css-loader
css-loader会找出css代码中@import 和 url()这样的导入语句,告诉webpack依赖这些资源,同时支持CSS modules,压缩CSS等功能
处理完后将结果给到style-loader
style-loader 会将CSS代码转换成字符串后,注入到js代码中,通过js向DOM增加样式,
如果想将css提取到单独文件中,可再使用ExtractTextPlugin插件提取

使用PostCSS

使用相应的loader 可以为css样式增加兼容浏览器的前缀,可以配置相应的配置文件,兼容下一代CSS语法

配置postcss.config.js文件

1
2
3
4
5
module.exports = {
plugins:[
require('postcss-cssnext') //postcss-cssnext支持下一代css语法编写
]
}

webpack配置

需要先将下一代语法转换成可识别css,同时添加前缀

1
2
3
4
5
6
modules:{
rules:[{
test:/\.scss$/,
use:['style-loader','css-loader','postcss-loader']
}]
}

安装依赖模块

npm i -D style-loader css-loader postcss-loader //webpack loader 依赖
npm i -D postcss-cssnext // postcss-loader 依赖

使用react

借助Bable

1.修改配置文件.babelrc,在preset中加入’react’:”presets”:[…[],”react”]
2.安装依赖模块
npm i -D react react-dom//react 基础依赖
npm i -D bable-preset-react //bable转换依赖

借助typescript

1.修改配置文件tsconfig.json

1
2
3
4
"compilerOptions":{
...,
"jsx":"react"//typescript本身支持jsx,开启JSX,支持react
},

2.修改webapck配置文件

1
2
3
4
5
6
7
8
9
resolve:{
extension:['.ts','.tsx',.js'] //typescript 原生支持jsx语法,只不过jsx语法文件后缀必须是tsx
}
modules:{
rules:[{
test:/\.tsx?$/,//同时匹配ts,tsx后缀文件
loader:'awesome-typescript-loader'
}]
}

3.安装依赖模块
npm i -D react react-dom//react 基础依赖
npm i -D @types/react @types/react-dom //react react-dom对应的ts接口描述模块,用于编译react,react-dom

使用VUE

引入

1.修改webapck配置文件

1
2
3
4
5
6
modules:{
rules:[{
test:/\.vue$/,
use:['vue-loader']
}]
}

2.安装依赖模块
npm i -S vue //vue框架依赖
npm i -D vue-loader css-loader vue-template-compiler //构建所需依赖
歌loader作用
vue-loader:解析转换.vue文件,提取其中的逻辑代码script样式代码style以及html模板template,然后交给对应的loader去处理
css-loader:加载vue-loader提取的CSS代码
vue-template-compiler:将vue-loader提取的HTML模板编译成对应可执行的js代码。

构建使用ts编写的vue

1.配置 tsconfig.json文件

1
2
3
4
5
6
"compilerOptions":{
"target":"es5",//构建es5版本的js,与VUE浏览器支持保持一致
"module":'es015',//使用es2015模块化格式
"strict":true,//开启严格模式,对this上数据属性进行严格推断
"moduleResolution":'node'
},

2.webpack配置

1
2
3
4
5
6
7
8
9
10
11
12
resolve:{
extension:['.ts','.js','.vue','json'] //增加对.ts,.vue文件支持
modules:{
rules:[{
test:/\.ts$/,//匹配ts后缀文件
loader:'ts-loader',
exclude:/node_modules/,
options:{
appendTsSuffixTo:[/\.vue$/] //让tsc将vue文件当成一个ts模块去处理,以解决module not found的问题,tsc本身不会处理.vue结尾文件
}
}]
}

3.新增文件vue-shims.d.ts
用以ts支持.vue结尾文件,可识别import 语句导入的.vue文件

1
2
3
4
declare module "*.vue"{
import Vue from "vue";
export default Vue
}

4.vue文件编写
script 部分

1
2
3
4
5
6
7
8
9
10
<script lang='ts'>//lang指明代码语法为ts
improt Vue from "vue"
export default Vue.extend({ //Vue.extend启用ts类型推断
data(){
return{
msg:'hello world'
}
}
})
</script

5.安装依赖
npm i -D ts-loader typescript

单页自动生成html

利用模板文件和插件生成

模板文件

模板文件用于描述哪些资源需要被以某种方式加入到输出的HTML文件中
资源链接URL字符串里问号前面的部分表示资源内容来自哪里,后面的参数表示这些资源注入的方式,可用&链接
_inline:资源是要引入动态资源
_dist:只在生产环境下才引入该资源
_dev:只在开发环境才引入该资源
_ie:只在IE浏览器中引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html lang="en">
<head>
<meta charset="utf-8">
<!--导入chunck app 中的css代码,可知要将css单独打包一个文件-->
<link rel="stylesheet" href="app?_inline">
<!--在这里引入/google_analytics的js代码-->
<script src="./google_analytics.js?_inline"></script>
</head>
<body>
<div id='app'></div>
<!--导入chunck app 中的js代码-->
<script type="text/javascript" src="app"></script>
</body>
</html>

webpack配置

使用web-webpack-plugin的webplugin自动生成index.html

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
const {WebPlugin} = require('web-webpack-plugin')
plugins:[
new WebPlugin({//自动生成index.html文件
template:'./template.html',//模板文件
filename:'index.html'//输出文件名
}),
new ExtractTextPlugin({
filename:`[name]_[contenthas:8].css` // 为输出的css文件名称加上Hash值
}),
new DefinePlugin({
'process.env':{//定义NODE_ENV环境变量为production,去除源码中只有开发时才需要的部分
NODE_ENV:JSON.stringify('production')
}
}),
new UglifyJsPlugin({//压缩输出的js代码
beautify:false,//最紧凑的输出
comments:false,//删除所有注释
compress:{
warnings:false,//删除没有用到的代码时,不发出警告
drop_console:true,//删除所有console语句,兼容IE
collapse_vars:true,//内嵌已定义但只用到了一次的变量
reduce_vars:true//提取出出现多次但没有定义成变量去引用的静态值
}
})

]

自动生成多个单页

需要解决的问题
1.要能够将公共代码提取出来,并能够注入到每个单页应用中
2.模板文件要支持注入公共文件和各个单页独自依赖的资源

模板文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html lang="en">
<head>
<meta charset="utf-8">
<!--在这里注入该页面所依赖但没有手动导入的CSS-->
<!--STYLE-->
<!--在这里引入/google_analytics的js代码-->
<script src="./google_analytics.js?_inline"></script>
</head>
<body>
<div id='app'></div>
<!-- 在这里注入该页面所依赖但没有手动导入的JS-->
<!--SCRIPT-->
</body>
</html>

此时模板文件被当作项目中所有单页的模板,因此不能直接写Chunck名称去引入资源,
因为需要被注入当前页面的Chunck名称不固定,每个单页都会有自己的名称
的作用在于保证该页面所依赖的资源都会被注入生成的HTML模板中
如果不存在,就注入到head标签最后
如果不存在,就注入到body标签最后

webpack配置

使用web-webpack-plugin的autoWebPlugin自动生成多个单页的index.html文件
但是对目录结构有要求,即所有单页各自的入口文件和依赖资源组成各自的一个文件夹,
多个文件夹放在同一目录A下,其他公共资源以及模板文件,webpack配置文件放在与A目录同级目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const {AutoWebPlugin} = require('web-webpack-plugin')
const autoWebPlugin = new AutoWebPlugin('pages',{ //pages 为各个单页的父目录A
template:'./template.html',//模板文件
postEntry:['./common.css'],//所有页面都依赖这份通用的CSS样式文件
commonChunk:{
name:'common',//提取公共代码chunk的名称
}
})
module.exports={
//AutoWebPlugin会为寻找到的所有单页生成对应的入口配置,
//autoWebPlugin.entry可以获取所有由autoWebPlugin生成的入口配置
entry:autoWebPlugin.entry({//可加入额外需要的Chunk入口}),
plugins:[autoWebPlugin]
}

构建基于react的同构应用

同构应用:写一份代码可同时在浏览器和服务器中运行的应用
能在服务器运行的原理核心是虚拟DOM
虚拟DOM好处:
1.操作DOM树是高耗时操作,可通过DOM diff算法找到两个不同Object的最小差异,得出最小的DOM操作
2.虚拟DOM在渲染时不仅可以通过操作DOM树表示结果,也可以有其他表示方式,例如将虚拟DOM渲染成字符串(服务器渲染)
或者渲染成手机APP原生UI组件(react Native)

服务器端构建配置

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
const path = require('path')
const nodeExternals = require('webpack-node-externals')
module.exports = {
entry:'./main_server.js'
target:'node',//目标运行环境是node.js,源码依赖的nodejs原生模块没必要打包进去
externals:[nodeExternals()],//不打包node_modules中的第三方组件
output:{
libraryTarget:'commonjs2',//输出CommonJS2规范,供nodejs的http服务器代码调用
fillname:'bundle.server.js',
path:path(resolve(__dirname,'./dist'))
},
module:{
rules:[
{
test:/\.js$/,
use:['babel-loader'],
exclude:path.resolve(__dirname,'node_modules'),
},
{
test:/\.css/,
use:['ignore-loader'],//css文件不能打包到服务端代码,影响服务端渲染性能
}
]
}

}

文件准备

一个仅包含根组件代码,不能包含渲染入口代码,而且需要导出根组件以供渲染入口调用的文件 rootComponent.js

1
2
3
4
5
6
7
import react,{Component} from 'react';
import 'main.css'
export class RootComponent extends Component{
render(){
return <h1>hello world </h1>
}
}

不同环境渲染入口文件,一个环境一个
main_server.js

1
2
3
4
5
6
import react from 'react';
import RootComponent from './rootComponent'
import {renderToString} from 'react-dom/server' //计算表示虚拟DOM的HTML形式字符串
export function render(){
return renderToString(<RootComponent>)
}

main_browser.js

1
2
3
4
import react from 'react';
import RootComponent from './rootComponent'
import {render} from 'react-dom' //操作浏览器DOM树展示出结果
render(<RootComponent>,window.document.getElementById('app'))

http.server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const express = require('express')
const {render } = require('./dist/bundle_server')
const app = express();
app.get('/',function(req.res){
res.send(`
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div id='app'>${render()}</div>
<!--导入WEBPACK输出的用于浏览器端渲染的js文件-->
<script src='./dist/bundle_browser.js'></script>
</body>
</html>
`)
})
app.use(express.static('.'));
app.listen(3000,function(){
console.log('app is listening 3000')
})

执行命令

webpack –config webpac_server.config.js 得到./dist/bundle_server.js
webpack –config webpac.config.js 得到./dist/bundle_browser.js
node ./http_server.js 启动 http 服务器,访问’localhost:3000’看到服务器返回了html

基于react 的Electron应用

Electron 可以使用开发web应用的技术去开发跨平台的桌面应用,例如Atom,VSCode
是nodejs和Chromium浏览器的结合体,用Chromium浏览器显示出的WEB页面作为应用的GUI,通过Nodejs和操作系统交互
当操作一个Electron应用的一个窗口时,实际是在操作一个网页,当操作需要操作系统完成时,网页会通过Nodejs和操作系统交互
优点
1.降低了开发门槛,只需掌握网页开发技术和Nodejs,大量Web开发技术和现成库可以复用于Electron
而且由于Electron环境内置了浏览器和Nodejs的API,在开发网页时除了可以使用浏览器提供的API,还可以会用Nodejs的API
2.由于Chromium浏览器和Nodejs都是跨平台的,所有Electron能做到在不同操作系统运行一份代码

构建

Electron 应用每个窗口对应一个网页,所以相当于需要构建多单页面应用
在网页JS代码中可能会调用Nodejs原生模块或者Electron模块,输出的代码依赖这些模块但由于这些模块都是内置支持的,
所有构建出的代码不能将这些模块打包进去
由于webpack内置了对Electron的支持,只需要告诉webpack我要在electron环境里运行就可以实现

1
target:'electron-renderer'

运行

安装electron执行环境到项目中
npm i -D electron

在项目目录下执行electron ./就可以启动桌面应用

构建Npm模块

要求

1.源码若采用es6编写需要转换成es5,并且要遵守commonjs规范,同时提供Source Map方便调式

解决:
使用Babel将es6转换成es5
通过开启devtool:’source-map’输出Source Map以发布调试
设置output.libraryTarget = ‘commonjs2’ 实现输出代码符合CommomJS2规范
2.若为UI组件,则依赖的其他资源文件如css文件也需要包含的发布的模块中

解决:通过css-loader,extract-text-webpack-plugin实现,将css打包到单独文件

3.尽量减少代码冗余,(例如,Babel将es6转换成es5时,会注入一些辅助函数,例如实现class,extend语法的辅助函数,Babel会在每个输出文件中中内嵌依赖的辅助函数,多文件依赖的话,就会造成辅助函数重复出现,造成代码冗余)减少发布出去的组件的代码文件大小

解决:通过引入相同函数解决重复代码
使用babel-plugin-transform-runtime 将嵌入辅助函数代码转成引入辅助函数
引入bable-runtime模块用于提供辅助函数

4.发布出去的组件代码中不能含有其依赖的模块代码,例如react,babel-runtime,而是让用户可选择性的安装,否则可能在其他模块也依赖相同模块时,造成重复打包

解决:配置externals将外部环境提供的模块屏蔽掉,不进行打包

webpack配置

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
29
30
31
32
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

module.exports = {
entry:'./src/index.js'
output:{
fillname:'index.js',
path:path.resolve(__dirname,'lib'),
libraryTarget:'commonjs2',
},
externals:/^(react|babel-runtime)/,
module:{
rules:[
{
test:/\.js$/,
use:['babel-loader'],
exclude:path.resolve(__dirname,'node_module'),
},
{
test:/\.css/,
use:ExtractTextPlugin.extract({
use:['css-loader']
})
}]
},
plugins:[
new ExtractTextPlugin({
fillname:'index.css'
})
],
devtool:'source-map'
}

.babellrc文件

1
2
3
4
5
6
7
8
9
10
{
'plugins':[
[
'transform-runtime',//默认自动注入ES6 API的polyfill
{
'polyfill':false//防止使用者在其他地方注入其他polyfill库,所以关闭注入polyfill功能
}
]
]
}

发布到Npm

修改package.json入口文件为打包后的文件

1
2
main:'lib/index.js',//webpack使用于构建不可分割的NPM模块,不能保持同源码结构一致例如如果打包lodash,会将所有工具函数打包进去,不适合仅用几个工具函数的场景
'jsnext:main':'src/index.js'//指出采用ES6编写的模块入口文件位置,便于实现Tree Sharking

离线应用 service workers打包

service workers了解

问题

1.如何生成sw.js文件
2.sw.js文件中的cacheFileList变量(代表被缓存文件的URL),需要根据输出文件列表所对应的URL来决定,不能写成静态值

解决

使用serviceworker-webpack-plugin,根据自定义sw.js生成含有输出文件列表的sw.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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const {WebPlugin} = require('web-webpack-plugin')
const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')

module.exports = {
entry:{
app:'./main.js' //Chunk app 的js执行入口文件
},
output:{
fillname:'[name].js',
publicPath:'',
},
module:{
rules:[
{
test:/\.css/,//增加对CSS文件支持,
use:ExtractTextPlugin.extract({//提取到单独文件
use:['css-loader']//压缩CSS代码
})
}]
},
plugins:[
//一个WebPlugin对应一个HTML
new WebPlugin({
template:'./template/html',//HTML模板文件所在的文件路径
fillname:'index.html'//输出的HTML文件名称
}),
new ExtractTextPlugin({
fillname:'[name].css'
}),
new ServiceWorkerWebpackPlugin({
//自定义的sw.js文件所在路径
//ServiceWorkerWebpackPlugin会将文件列表注入生成的sw.js
entry:path.join(__dirname,'sw.js')
})
],
devServer:{
//Service workers依赖HTTPs,使用devServer提供https功能
https:true
}
}

构建出的sw.js文件会在头部注入一个变量serviceWorkerOption.assets到全局,里面存放着所有需要被缓存的文件的URL列表
因此需要将sw.js文件中写成静态值的cacheFileList替换成serviceWorkerOption.assets
var cacheFileList = global.serviceWorkerOption.assets

安装

npm -i -D web-webpack-plugin serviceworker-webpack-plugin

代码检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
module:{
rules:[
{
test:/\.js$/,
exclude:/node_module/,//不检查node_module下文件
loader:'tslint-loader',//整合typeScript代码检查
enforce:'pre'//将执行顺序放到最前面,防止其他Loader将处理后的代码交给tslint-loader检查
},{
test:/\.js$/,
exclude:/node_module/,//不检查node_module下文件
loader:'eslint-loader',//整合eslint检查代码
enforce:'pre'//将执行顺序放到最前面,防止其他Loader将处理后的代码交给tslint-loader检查
}]
},
plugin:[
new StyleLintPlugin() //整合stylelint,检查css代码,可以解析SCSS,Less
]
}

导致问题

1.执行检查步骤计算量大,或导致webpack构建变慢
2.整合代码检查到webpack后,输出的错误信息是通过行号来定位错误的,没有编辑器集成显示错误直观

解决

1.将代码检查步骤当道代码提交时,即在代码提交前调用以上检查工具去检查代码,只有检查都通过时才提交代码,这样保证仓库内代码都经过检查
2.使用集成了代码检查功能的编辑器,让编辑器实时,直观的显示错误
安装 npm i -D husky 接入git hook,通过git 的hook功能做到在提交代码前触发执行,husky会通过Npm Script Hook自动配置好HOOK
但需要在package.json定义脚本,如下

1
2
3
4
5
6
7
{
"script":{
"precommit":"npm run lint",//git commit 前执行的脚本
"prepush":'lint',//git push前会执行的脚本
"lint":'eslint && stylelint'//调用eslint,stylelint检查工具
}
}

在nodejs中使用

通过调用Webpack的API,执行构建

一次构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const webpack = require('webpack')
//import webpack from 'webpack'

1.使用模块方式一
webpack({
//webpack配置,和webpack.config.js一样
},(err,stats)=>{
if(err || stats.hasErrors()){
//构建过程出错
}
//成功执行完构建
})
2.使用模块方式二
const config = require('./webpack.config.js')
webpack(config,callback)

启动监听模式

1
2
3
4
5
6
7
8
9
10
11
12
13
//不给webpack传递callback就会返回compiler实例,用于控制启动,而不是像上面那样立即启动
const compiler = webpack(config)
//调用compiler.watch并以监听模式启动,返回watching用于关闭监听
const watching = compiler.watch({
//watchOptions
aggregateTimeout:300
},(err,stats)=>{
//每次因文件发生变化而重新执行完/构建后
})
//调用watching.close关闭监听
watching.close(()=>{
//在监听关闭后
})

加载图片相关的loader

1.file-loader
将js和CSS中导入图片的地址替换成 webpack输出文件的地址,输出文件名是根据内容计算出的HASH值

1
2
3
4
rules:[{
test:/\.png$/,
use:['fill-loader']
}]

2.url-loder
将图片转base64直接注入到引入的地方,
一般利用url-loder将网页需要用到的小图片资源注入代码中,以减少加载次数,为一个很小图片而2新建一次HTTP连接不划算
如果图片体积太大会导致js,CSS文件过大而带来网页加载缓慢的问题

1
2
3
4
5
6
7
8
9
10
11
12
rules:[{
test:'/\./png$/',
use:[{
loader:'url-loader',
options:{
//30KB以下文件采用url-loader,控制文件大小
limit:1024*30,
//否则采用file-loader,默认值是file-loader
fallback:'file-loader'
}
}]
}]

还可以以下方式优化,同样适用于其他二进制类型的资源,如PDF,SWF
A.通过imagin-webpack-plugin压缩图片
B.通过webpack-spritesmith插件制作雪碧图

以上两个loader都可用于处理svg图片,但svg文件是文本格式文件,还有其他方法

3.raw-loader
可以将文本文件内容读取出来,注入js/CSS中
由于会直接返回svg的文本内容,并且无法通过CSS展示SVG的文本内容,因此采用该loader后无法在CSS中导入SVG

1
2
3
4
rules:[{
test:/\./svg$/,
use:'raw-loader'
}]

4.svg-inline-loader
类似raw-loader,但会分析SVG内容,去除其中不必要的部分代码,以减少SVG文件大小,相当于增加了对SVG的压缩能力

1
2
3
4
rules:[{
test:/\.svg$/,
use:['svg-inline-loader']
}]

DevServer 实现

webpack-dev-server本身基于webpack-dev-middleware和expressjs,而webpack-dev-middleware是一个express.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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const express = require('express')
const wepack = require('webpack')
const webpackMiddleware = require('webpack-dev-middleware')

const config = require('./webpack.config.js')
const app = expree()
const compiler = webpack(config)
app.use(webpackMiddleware(compiler,{
//在webpack-dev-middleware支持的所有配置项中
//只有publicPath属性为必填项,其他都是选填项

//webpack输出资源绑定HTTP服务器上的根目录
//同WEBPACK配置中的publicPath
publicPath:'/assets/',

//不输出info类型的日志到控制台,只输出warn和error类型的日志
noInfo:false,
//不输出任何类型的日志到控制台
quiet:false,
//切换到懒惰模式,意味着不监听文件的变化,只会在有请求时再编译对应的文件,适合页面很多的项目
lazy:true,
//watchOptions,只在非懒惰模式下才有效
watchOptions:{
aggregationTimeout:300,
poll:true
},

//默认的URL路径,默认是'index.html'
index:'index.html',
//自定义HTTP头
headers:{'X-Custom-Header':'yes'},
//为特定后缀的文件添加HTTP mimeTypes,作为文件类型映射表
mimeTypes:{'text/html':['phtml']},

//统计信息输出样式
stats:{
colors:true
},
//自定义输出日志的展示方法
reporter:null,
//开启或关闭服务端渲染
serverSideRender:false

}))
//webpackMiddleware函数返回一个Expressjs中间件,该中间件有俩个功能
//1.接收来自webpack compiler实例输出的文件,但不会将文件输出到硬盘中,而会保存在内存中
//2.在express.js上注册路由,拦截HTTP收到的请求,根据请求路径响应对应文件内容

//webpack-dev-middleware没有模块热替换功能,但Devserver有,
//可通过webpack-hot-middleware中间件来支持模块热替换,响应用于替换老模块的资源
app.use(require('webpack-hot-middleware')(compiler))
//将项目根目录作为静态资源目录,用于服务器HTML文件
app.use(express.static('.'))

app.listen(3000)

响应模块热替换功能还需要作如下配置
1.修改webpack.config.js文件,加入HotModuleReplacementPlugin插件
相当于执行 webpack-dev-server –hot工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const HotModuleReplacementPlugin= require('webpack/lib/HotModuleReplacementPlugin')
module.exports={
entry:[
'webpack-hot-middleware/client',//为了支持模块热替换注入代理客户端
'./src/main.js'
],
output:{
filename:'bundle.js'
},
plugin:[
//为了支持模块热替换,生成.hot-update.json文件
new HotModuleReplacementPlugin(),
]
}

2.修改入口文件main.js,加入替换逻辑

1
2
3
4
//在文件末尾加入
if(module.hot){
moudule.hot.accept()
}

3.安装以上配置中用到的依赖
npm i -D webpack-dev-middleware webpack-hot-middleware express

My Little World

react Native 基础知识

发表于 2019-03-05

常用标签

Text:用来显示文本
View:相当于div或者span这样的容器,常用作其他组件的容器,来帮助控制布局和样式
Image:显示图片,属性source指定图片地址,也可以使用style属性控制尺寸

知识点

props:子组件通过this.props拿到父组件使用子组件时传递进来的属性值
state:自定义组件控制内部逻辑的变量,同react的state,在constructor里面初始化
StyleSheet:使用StyleSheet.create()创建样式,相当于style标签,不过使用时在style中使用

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 React, { Component } from 'react';
import { AppRegistry, StyleSheet, Text, View } from 'react-native';

export default class LotsOfStyles extends Component {
render() {
return (
<View>
<Text style={styles.red}>just red</Text>
<Text style={styles.bigBlue}>just bigBlue</Text>
<Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
<Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
</View>
);
}
}

const styles = StyleSheet.create({
bigBlue: {
color: 'blue',
fontWeight: 'bold',
fontSize: 30,
},
red: {
color: 'red',
},
});

在style属性中可以直接写驼峰式CSS属性进行样式调整
但设置width和height是不带单位的,React Native 中的尺寸都是无单位的,表示的是与设备像素密度无关的逻辑像素点。

Text

相当于span,但可以被’\n’换行
用作子元素时,
如果父元素为Text时,多个Text子元素尽可能放一行,一行装不下时,自动换行,子元素Text标签会继承父元素Text一部分样式
如果父元素为View时,每个Text子元素成为flex布局的一个块,当容器不够宽时,每个块自动换行,块与块之间不影响
必须把文本放在Text组件中,不能直接放在View中

属性

selectable:是否可以长按选择文本,以便复制和粘贴
selectionColor[Andorid]:选中时高亮颜色
suppressHighlighting[IOS]:设为true时,当文本被按下会没有任何视觉效果。默认情况下,文本被按下时会有一个灰色的、椭圆形的高光
ellipsizeMode:表示当 Text 组件无法全部显示需要显示的字符串时如何用省略号进行修饰
————head : 从文本内容头部截取显示省略号。例如: “…efg”
————middle : 在文本内容中间截取显示省略号。例如: “ab…yz”
————tail : 从文本内容尾部截取显示省略号。例如: “abcd…”
————clip : 不显示省略号,直接从尾部截断。
numberOfLines:文本过长时,最多折叠多少行,执行ellipsizeMode设置的效果
onLayout:加载时或者布局变化以后调用,参数为:{nativeEvent: {layout: {x, y, width, height}}}
onLongPress:当文本被长按以后调用此回调函数
onPress:当文本被点击以后调用此回调函数。
llowFontScaling:制字体是否要根据系统的“字体大小”辅助选项来进行缩放。默认值为true。

TextInput

有些属性仅在multiline为true或者为false的时候有效,例如当multiline=false时,为元素的某一个边添加边框样式(例如:borderBottomColor,borderLeftWidth等)将不会生效
在安卓上长按选择文本会导致windowSoftInputMode设置变为adjustResize,这样可能导致绝对定位的元素被键盘给顶起来。要解决这一问题你需要在AndroidManifest.xml中明确指定合适的windowSoftInputMode( https://developer.android.com/guide/topics/manifest/activity-element.html )值,或是自己监听事件来处理布局变化。

属性

placeholder:同input
maxLength:限制文本框中最多的字符数。使用这个属性而不用JS逻辑去实现,可以避免闪烁的现象。
multiline:如果为true,文本框中可以输入多行文字。默认值为false。注意安卓上如果设置multiline = {true},文本默认会垂直居中,可设置textAlignVertical: ‘top’样式来使其居顶显示
numberOfLines:设置输入框的行数
allowFontScaling:控制字体是否要根据系统的“字体大小”辅助选项来进行缩放。默认值为true。
autoCapitalize:控制TextInput是否要自动将特定字符切换为大写
————characters: 所有的字符。
————words: 每个单词的第一个字符。
————sentences: 每句话的第一个字符(默认)。
————none: 不切换。
autoCorrect:如果为false,会关闭拼写自动修正。默认值是true
autoFocus:如果为true,在componentDidMount后会获得焦点。默认值为false。
blurOnSubmit:如果为true,文本框会在提交的时候失焦。对于单行输入框默认值为true,多行则为false。注意:对于多行输入框来说,如果将blurOnSubmit设为true,则在按下回车键时就会失去焦点同时触发onSubmitEditing事件,而不会换行。
caretHidden:如果为true,则隐藏光标。默认值为false。
clearButtonMode:是否要在文本框右侧显示“清除”按钮。仅在单行模式下可用。默认值为never。
clearTextOnFocus:如果为true,每次开始输入的时候都会清除文本框的内容。
defaultValue:
提供一个文本框中的初始值。当用户开始输入的时候,值就可以改变。在一些简单的使用情形下,如果你不想用监听消息然后更新value属性的方法来保持属性和状态同步的时候,就可以用defaultValue来代替。
editable:如果为false,文本框是不可编辑的。默认值为true
enablesReturnKeyAutomatically:如果为true,键盘会在文本框内没有文字的时候禁用确认按钮。默认值为false。
inlineImageLeft:指定一个图片放置在左侧。图片必须放置在/android/app/src/main/res/drawable目录下,经过编译后按如下形式引用(无路径无后缀)

1
2
3
<TextInput
inlineImageLeft='search_icon'
/>

inlineImagePadding:给放置在左侧的图片设置padding样式。
keyboardAppearance:指定键盘的颜色。
keyboardType:决定弹出何种软键盘类型

响应

onBlur:当文本框失去焦点的时候调用此回调函数。
onChange:当文本框内容变化时调用此回调函数。回调参数为{ nativeEvent: { eventCount, target, text} }
onChangeText:当文本框内容变化时调用此回调函数。改变后的文字内容会作为参数传递。
onEndEditing:当文本输入结束后调用此回调函数。
onKeyPress:当一个键被按下的时候调用此回调。传递给回调函数的参数为{ nativeEvent: { key: keyValue } },其中keyValue即为被按下的键。会在onChange之前调用。注意:在Android上只有软键盘会触发此事件,物理键盘不会触发。
onSubmitEditing:此回调函数当软键盘的确定/提交按钮被按下的时候调用此函数。如果multiline={true},此属性不可用。

Button

样式单一,可能不适合统一UI样式

属性

title:按钮名
color:文本的颜色(iOS),或是按钮的背景色(Android)
disabled:禁用

响应

onPress:点击触发

TouchableHighlight

用来封装可以点击的元素,来制作按钮或者链接。注意此组件的背景会在用户手指按下时变暗。
使其可以正确响应触摸操作。当按下的时候,封装的视图的不透明度会降低,同时会有一个底层的颜色透过而被用户看到,使得视图变暗或变亮
只支持一个子节点(不能没有子节点也不能多于一个)。如果你希望包含多个子组件,可以用一个View来包装它们

属性

activeOpacity:指定封装的视图在被触摸操作激活时以多少不透明度显示(0到1之间,默认值为0.85)。需要设置underlayColor。
underlayColor:有触摸操作时显示出来的底层的颜色。

响应

onHideUnderlay:底层的颜色被隐藏的时候调用。
onShowUnderlay:当底层的颜色被显示的时候调用。

TouchableNativeFeedback

用来封装可以点击的元素,在用户手指按下时形成类似墨水涟漪的视觉效果
它只支持一个单独的View实例作为子节点

属性

background:决定在触摸反馈的时候显示什么类型的背景,它接受一个有着type属性和一些基于type属性的额外数据的对象。
一般用本组件的几个静态方法来创建这个对象
——————SelectableBackground():创建一个对象,表示安卓主题默认的对于被选中对象的背景
——————SelectableBackgroundBorderless():创建一个对象,表示安卓主题默认的对于被选中的无边框对象的背景
——————Ripple(color: string, borderless: boolean):创建一个对象,当按钮被按下时产生一个涟漪状的背景,你可以通过color参数来指定颜色,如果参数borderless是true,那么涟漪还会渲染到视图的范围之外

ScrollView

一个通用的可滚动的容器,你可以在其中放入多个组件和视图,而且这些组件并不需要是同类型的,适合用来显示数量不多的滚动元素

属性

horizontal:为true时,元素水平排列,默认false,垂直排列
scrollsToTop:当此值为true时,点击状态栏的时候视图会滚动到顶部。默认值为true
indicatorStyle:设置滚动条的样式。默认default 同black,’white’白色滚动条
overScrollMode:覆盖默认的overScroll模式
——————‘auto’ : 默认值,允许用户在内容超出视图高度之后可以滚动视图。
——————‘always’ : 无论内容尺寸,用户始终可以滚动视图。
——————‘never’ : 始终不允许用户滚动视图。
stickyHeaderIndices:一个子视图下标的数组,用于决定哪些成员会在滚动之后固定在屏幕顶端。举个例子,传递stickyHeaderIndices={[0]}会让第一个成员固定在滚动视图顶端。这个属性不能和horizontal={true}一起使用。
scrollEnabled:当值为false的时候,内容不能滚动,默认值为true。注意即便禁止用户滚动,你也仍然可以调用scrollTo来滚动。
showsHorizontalScrollIndicator:为true的时候,显示一个水平方向的滚动条。
showsVerticalScrollIndicator:为true的时候,显示一个垂直方向的滚动条
refreshControl:用于为ScrollView提供下拉刷新功能。只能用于垂直视图,即horizontal不能为true。
pagingEnabled:当值为true时,滚动条会停在滚动视图的尺寸的整数倍位置。这个可以用在水平分页上。默认值为false。注意:垂直分页在Android上不支持。

响应

onScrollBeginDrag:当用户开始拖动此视图时调用此函数
onScrollEndDrag:当用户停止拖动此视图时调用此函数。
onScroll:在滚动的过程中,每帧最多调用一次此回调函数。调用的频率可以用scrollEventThrottle属性来控制。
onMomentumScrollBegin:滚动动画开始时调用此函数。
onMomentumScrollEnd:滚动动画结束时调用此函数
scrollTo(([y]: number),([x]: number),([animated]: boolean),([duration]: number)):滚到指定位置时
scrollToEnd(([options]: {animated: boolean, duration: number}));滚到视图底部

FlatList

用于显示一个垂直的滚动列表,其中的元素之间结构近似而仅数据不同
更适于长列表数据,且元素个数可以增删。和ScrollView不同的是,FlatList并不立即渲染所有元素,而是优先渲染屏幕上可见的元素。

属性

data:数据源
renderItem:返回重复的子组件
ItemSeparatorComponent:行与行之间的分隔线组件。不会出现在第一行之前和最后一行之后。值是一个组件
ListEmptyComponent:列表为空时渲染该组件。
ListHeaderComponent:头部组件
ListFooterComponent:尾部组件。
horizontal:设置为 true 则变为水平布局模式

extraData:如果有除data以外的数据用在列表中(不论是用在renderItem还是头部或者尾部组件中),请在此属性中指定。同时此数据在修改时也需要先修改其引用地址(比如先复制到一个新的 Object 或者数组中),然后再修改其值,否则界面很可能不会刷新。

initialNumToRender:指定一开始渲染的元素数量,最好刚刚够填满一个屏幕,这样保证了用最短的时间给用户呈现可见的内容。注意这第一批次渲染的元素不会在滑动过程中被卸载,这样是为了保证用户执行返回顶部的操作时,不需要重新渲染首批元素

initialScrollIndex:开始时屏幕顶端的元素是列表中的第 initialScrollIndex个元素, 而不是第一个元素。如果设置了这个属性,则第一批initialNumToRender范围内的元素不会再保留在内存里,而是直接立刻渲染位于 initialScrollIndex 位置的元素。需要先设置 getItemLayout 属性。

keyExtractor:此函数用于为给定的 item 生成一个不重复的 key。Key 的作用是使 React 能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取item.key作为 key 值。若item.key也不存在,则使用数组下标。

onEndReachedThreshold:决定当距离内容最底部还有多远时触发onEndReached回调。注意此参数是一个比值而非像素单位
refreshing:在等待加载新数据时将此属性设为 true,列表就会显示出一个正在加载的符号

响应

onEndReached:当列表被滚动到距离内容最底部不足onEndReachedThreshold的距离时调用
onRefresh:如果设置了此选项,则会在列表头部添加一个标准的RefreshControl控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing属性
onViewableItemsChanged:在可见行元素变化时调用。可见范围和变化频率等参数的配置请设置viewabilityConfig属性
scrollToOffset():滚动列表到指定的偏移(以像素为单位),等同于ScrollView的scrollTo方法。

SectionList

要渲染的是一组需要分组的数据,也许还带有分组标签的数据
文档

Platform

Platform是一个模块,不是组件,用来进行平台检测
Platform.OS,在 iOS 上会返回ios,而在 Android 设备或模拟器上则会返回android。
Platform.select({ios:{},android:{}}) 可以以 Platform.OS 为 key,从传入的对象中返回对应平台的值
Platform.Version, Android 的 api level,值为数字,ios上为一个表示当前系统版本的字符串
当不同平台的代码逻辑较为复杂时,最好是放到不同的文件里,这时候我们可以使用特定平台扩展名。React Native 会检测某个文件是否具有.ios.或是.android.的扩展名,然后根据当前运行的平台自动加载正确对应的文件。
比如你可以在项目中创建下面这样的组件:

1
2
BigButton.ios.js
BigButton.android.js

然后去掉平台扩展名直接引用:

1
import BigButton from './BigButton';

Image

用于管理 iOS 和 Android 应用中的图片
图片文件的查找会和 JS 模块的查找方式一样,如果有my-icon.ios.png和my-icon.android.png,Packager 就会根据平台而选择不同的文件
可以使用@2x,@3x这样的文件名后缀,来为不同的屏幕精度提供图片,Packager 会打包所有的图片并且依据屏幕精度提供对应的资源,如果没有图片恰好满足屏幕分辨率,则会自动选中最接近的一个图片。
为了使新的图片资源机制正常工作,require 中的图片名字必须是一个静态字符串(不能使用变量!因为 require 是在编译时期执行,而非运行时期执行!)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 正确
<Image source={require('./my-icon.png')} />;

// 错误
var icon = this.props.active ? 'my-icon-active' : 'my-icon-inactive';
<Image source={require('./' + icon + '.png')} />;

// 正确
var icon = this.props.active
? require('./my-icon-active.png')
: require('./my-icon-inactive.png');
<Image source={icon} />;
//通过这种方式引用的图片资源包含图片的尺寸(宽度,高度)信息,如果需要动态缩放图片(例如,通过 flex),可能必须手动在 style 属性设置{ width: null, height: null }。

require语法也可以用来静态地加载你项目中的声音、视频或者文档文件,包括.mp3, .wav, .mp4, .mov, .htm 和 .pdf等
注意的是视频必须指定尺寸而不能使用flex样式

要在 App 中显示的图片并不能在编译的时候获得,又或者有时候需要动态载入来减少打包后的二进制文件的大小。这些时候,与静态资源不同的是,需要手动指定图片的尺寸

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
29
// 正确
<Image source={{uri: 'https://facebook.github.io/react/logo-og.png'}}
style={{width: 400, height: 400}} />
//或者 指定请求参数
<Image
source={{
uri: 'https://facebook.github.io/react/logo-og.png',
method: 'POST',
headers: {
Pragma: 'no-cache',
},
body: 'Your Body goes here',
}}
style={{width: 400, height: 400}}
/>
//或者 引用base64
<Image
style={{
width: 51,
height: 51,
resizeMode: 'contain',
}}
source={{
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg==',
}}
/>
// 错误
<Image source={{uri: 'https://facebook.github.io/react/logo-og.png'}} />

读取本地静态图片(使用require(‘./my-icon.png’)语法)则无需指定尺寸,因为它们的尺寸在加载时就可以立刻知道。

ImageBackground

用于设置背景图,把需要背景图的子组件嵌入其中即可,需要设置大小

react-navigation

导航器,控制跳转
资料

InteractionManager

确保在执行繁重工作之前所有的交互和动画都已经处理完毕。

1
2
3
4
5
6
7
8
9
InteractionManager.runAfterInteractions(() => {
// ...需要长时间同步执行的任务...
});
//允许应用注册动画,在动画开始时创建一个交互“句柄”,然后在结束的时候清除它
var handle = InteractionManager.createInteractionHandle();
// 执行动画... (`runAfterInteractions`中的任务现在开始排队等候)
// 在动画完成之后
InteractionManager.clearInteractionHandle(handle);
// 在所有句柄都清除之后,现在开始依序执行队列中的任务

requestAnimationFrame(): 用来执行在一段时间内控制视图动画的代码
setImmediate/setTimeout/setInterval(): 在稍后执行代码。注意这有可能会延迟当前正在进行的动画。
runAfterInteractions(): 在稍后执行代码,不会延迟当前进行的动画。

My Little World

react Native 环境配置

发表于 2019-03-04

其实按照官网一步一步来就好了,也不用翻墙,基本上,就是耗时间
我配置的是window android环境
中文文档
1.安装node
可以直接使用nvm安装,但版本必须是8.或者10.
带来的问题是,nvm切换版本后,npm可能找不到,可以直接用全局安装的yarn代替
npm 与 Yarn 常用命令对比
或者直接将npm文件夹从能用的版本移到不能用的版本

2.python2
官网说不支持Python3.X,因为很早之前安装的python没管,今天一看是3.*,能跑起来

3.JDK
下载地址
我下载的这个
native1
安装过程注意,jdk和jre使用不同文件夹
安装与环境配置
4.Android Studio
按文档步骤安装好后,需要先创建项目再创建虚拟机
构建虚拟机过程文档应该需要翻墙,但其实可以直接百度
构建虚拟机
相当于在PC上调试
5.链接真机测试
数据线链接手机和电脑,在git里跑’react-native run-android’
遇到的问题
1.第一次跑时,出现:unable to load script from assets
解决如下:
第一步:在Android/app/src/main目录下创建一个空的assets文件夹。
第二步:执行

1
react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/

再重新跑react-native run-android
2.摇晃手机reload 后:could not connect to development sever
华为手机andorid版本较低,4.*
点击dev setting,选择Debug server host&port for device,填入PC电脑的IP地址和端口号
3.引入使用native-nivigation和react-native-gesture-handler后运行报错
settings file ‘E:\git\mobileApp\android\settings.gradle’: 3: unexpected char: ‘\’ @ line 3, column 1
解决: android下setiing.gradle,’\’换成‘/’

1
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')

My Little World

webpack配置项

发表于 2019-03-03

webpack在启动后会从Entry里配置的Moudule开始,递归解析Entry依赖的所有Module,每找到一个module,就会根据配置的loader去找对应的转换规则,
对Module进行转换后,在解析当前Module依赖的Module。这些模块会以Entry为单位进行分组,一个Entry及其所有依赖Module被分到一个组,也就是一个Chunk
最后,webpack会将所有Chunk转换成文件输出。webpack会在恰当的时机执行Plugin里定义的逻辑

entry

入口,webpack执行构建的第一步将从Entry开始,可抽象成输入
配置:
方式1: 直接一个文件路径字符串:’./app/entry1’
方式2: 文件路径字符串数组:[‘./app/entry1’,’./app/entry2’]
方式3: 文件路径字符串对象:{A:’./app/entryA’,B:’./app/entryB’}
方式4: 函数,动态导出以上三种形式
方式1,2最后只会有一个文件会被导出,即导出一个Chunk,名为main
方式3导出多个Chunk,名称分别对应对象KEY值

output

输出结果,在webpack经过一系列处理并得到最终想要的代码后输出结果
配置:

filename

输出文件名
方式1:静态,名字写死,如bundle.js,适用于只有一个导出Chunk,一个输出文件
方式2:利用Chunk内置变量,动态拼接生成文件名,例如’[name].js’
Chunk内置变量有:id,name,hash(name的hash,可指定长度,例如[hash:8]),chunkhash(chunk内容的hash)
相关:ExtractTextWebpackPlugin使用contenthash表示文件内容hash,但提取的内容为代码本身,不是一组模块组成的chunck内容

chunkFilename

无具体入口的chunk输出的文件名
例如通过commonChunkPlugin生成的文件,或者使用import动态加载生成的文件等
因为都是chunk输出文件,所以内置变量同filename的chunk

path

输出文件的存放目录
配置:绝对路径的字符串
内置变量:仅有一个,hash,代表一次编译操作的hash值

publishPath

配置发布到线上资源的URL前缀
配置:字符串
内置变量:仅有一个,hash,代表一次编译操作的hash值

crossOriginloading

配置异步插入的script标签的crossorign值
配置:
anonymous,默认,不带cookie
use-credentials,加载时,带cookie

library

导出库的名称,与libraryTarget配合使用
配置:字符串

libraryTarget

以何种方式导出库
配置:以下字符串
‘var’|’commonjs’|’commonjs2’|’this’|’window’|’global’

libraryExport

在libraryTarget配置’commonjs’|’commonjs2’时,配置导出模块中哪些子模块被导出

module

模块处理规则

rules

数组,每一项是处理同一类文件的相关配置对象

test

处理哪些文件,文件名命中规则,
配置:字符串

include

在什么目录范围内匹配
配置:字符串/字符串数组(每一项是或的关系,只要满足一个条件就会命中)

exclude

排除哪些目录范围,再匹配
配置:字符串/字符串数组(每一项是或的关系,只要满足一个条件就会命中)

use

配置loader
loader可以看作具有文件转换功能的翻译员,告诉webpack在遇到哪些文件时使用哪些loader去加载和转换
配置:数组,每一项可以是是字符串,或者对象
默认执行顺序为数组倒序执行,即最末尾先执行
字符串:loader名称
对象:对于该loader的一些相关配置

loader

配置:字符串,loader名

option

配置:对象,给loader处理函数传入的参数

enforce

更改loader执行顺序
配置:
pre: 放在执行顺序最前面
post:放在执行顺序最后

noparse

功能类似于exclude,排除不需要进行接续处理的文件,
此类文件中不能采用模块化方式编写,即不应包含import,require,define等语句,
否则导致无法在浏览器下执行
配置:字符串,文件路径

parse

更细粒度地配置哪些语法被解析,哪些不被解析,精确到语法层面
{cmd:false,commonjs:false….}

resolve

配置如何寻找模块对应的文件

alias

通过将路径前缀指定别名,进行映射
配置:对象,key为别名,值为实际路径
例:{components:’./src/components’,…}

第三方模块一般会包含两套代码
一套采用comonjs规范的模块代码,这些文件都放在lib目录下,以package.json中指定的react.js为模块入口
一套是将React所有相关代码打包好的完整代码放到一个单独文件中,这些代码没有采用模块化,可以直接执行,
其中非压缩文件(dist/xxx.js)用于开发环境,里面包含检查和警告的代码,压缩的代码(dist/xxx.min.js)用于线上环境
默认情况下,webpack会从入口文件./node_module/xxx/xxx.js开始递归解析和处理依赖的几十个文件,会是一个耗时操作
可以通过配置reaolve.alias,让webpack处理第三方库时,直接使用单独完整的xxx.min.js,从而跳过耗时的递归解析操作

mainFields

决定优先使用第三方模块的哪份代码,按数组顺序查找,使用找到的第一份文件
默认值和target有关,
当target为web或者webwork时,值是[‘browser’,’module’,’main’]
当target为其他值时,值时[‘module’,’main’]
配置:第三方模块导出文件的关键字
例:[‘jsnext:main’,’brower’,’main’]

extensions

导入语句没有带文件后缀时,尝试寻找的后缀代表
配置:后缀字符串数组
例:[‘.ts’,’.js’,’json’]

modules

去哪些目录下寻找第三方模块,
默认node_modules 含义是先去当前目录的./node_module目录下找,如果没有找到,就去上一级目录../node_module中去找
再没有就去../../node_module中找,一次类推
配置:字符串数组
例:[‘.src/components’,’node_modules’],
可以直接在页面中import ‘.src/components’里面的模块,不用再写相对路径

descriptionFiles

配置第三方模块名
配置:字符串数组

enforceExtension

是否强制导入语句中必须带后缀
true:必须带
false:不用必须带

enforceModuleExtension

只对node_module文件夹下模块生效
通常设置false,与enforceExtension:true配合,兼容第三方模块,使用时不加后缀

plugin

扩展插件,在webpack构建流程中的特定时机注入扩展逻辑,来改变构建结果或者做一些自定义的操作
比如将css文件单独打包,或者抽离公共模块
不使用plugin处理css文件的原理大概是将CSS内容用JS字符串存储起来,在网页执行JS时,通过DOM操作,动态向HTML head标签里插入HTML style标签
使用plugin将css抽离,是从打包好的js文件再提取出来
配置:plugin实例数组,使用new操作符调用构造函数,同时将参数传递进去
例:[new commonsChunkPlugin({name:’common’})]

devServer

配置针对使用webpack-dev-server启动webpack时的一些配置,相当于配置webpack-dev-server
devServer 会启动一个http服务器,用于服务网页请求,
同时会启动webpack,将webpack构建的文件保存在内存中,在要访问输出的文件时,必须通过HTTP服务访问,
同时会开启webpack的监听模式,devServer会让webpack在构建出的js代码里注入一个代理客户端用于控制网页,
网页和devServer之间通过webSocket协议通信,以方便devServer主动向客户端发送命令,
devServer在收到来自webpack的文件变化通知时,通过注入的客户端控制自动刷新网页,做到实时预览。
1.由于devServer不会理会配置的output.path属性,所以获取打包文件时,应该依据HTTP获取,不再依据output.path获取
因此要注意使用devServer之后打包文件的获取路径要进行更改。
2.index.html因为脱离js模块化系统,webpack不知道它的存在,故,更改index.html,不会被监听到
3.除了通过重新刷新整个网页来实现预览,devServer还有一种被称作模块热替换的刷新技术。
模块热替换能做到在不重新加载整个网页的情况下,通过将已更新的模块替换老模块,再重新执行一次来实现实时预览。
模块热替换相对于默认的刷新机制能提供更快的响应速度和更好的开发体验。
可以在执行devServer时加上 –hot开启
4.启动webpack时,加上–devtool source-map 可生成souce-map,方便在浏览器中调试源代码

配置
hot是否开启热加载(热替换)
模块热替换原理:
类似于自动刷新,都需要向要开发的网页中注入一个代理客户端来连接DevServer和网页,区别在于热替换时会多出三个用于热替换的文件
在发生文件变化时,会重新生成一个用于替换老模块的补丁文件,补丁文件中会包含新编译的代码,页面会使用新编译的代码
当子模块发生更新时,更新事件会一层一层向上传递,会从根组件传递到main.js,直到有某层文件接收了当前变化的模块,
就会去执行自定义的逻辑,如果事件一直往上抛,到最外层都没有文件接收它,则会直接刷新网页,
最直观的就是修改main.js时,会发生整个页面刷新
而对于.css文件,在使用style-loader处理时会注入用于接收CSS的代码,所以在修改.css文件时,会触发模块热替换

inline
开启实现实时预览,自动刷新;
不开启,使用iframe方式运行开发的网页,需要去localhost:8080/webpack-dev-pack实时预览
historyApiFallback:用于H5History API单页应用开发
contentBase:devServer HTTP服务器文件根目录
headers:在HTTP响应中注入HTTP响应头
host:监听地址
port:监听端口
allowedHosts:只有HTTP请求的HOST在列表中才正常返回
disabledHostCheck:是否关闭用于DNS重新绑定的HTTP请求的HOST检查
https:是否运行在https上
clientloglevel:配置客户端日志等级

target

构建不同运行环境的代码

devtool

有很多选项可配置,选项之间可以随意组合
6个关键配置项:
eval:用eval语句包裹需要安装的模块
source-map:生成独立的Source Map文件
hidden:不在JS文件中指出 Source Map文件的位置,这样浏览器就不会自动加载Source Map
inline:将生成的Source Map转换成BASE64格式内嵌在JS文件中
cheap:在生成的Source Map文件中不会包含列信息,这样计算量更小,输出的Source Map文件更小,同时Loader输出的Source Map不会被采用
module:来自Loader的Source Map被简单的处理成每行一个模块

‘source-map’

仅设置source-map会造成以下两个问题
1.会输出质量最高且最详细的Source Map,会造成构建速度缓慢,特别是开发过程中需要频繁修改时会增加等待时间
2.会将Source Map暴露,若构建发布到线上代码的Source Map暴露等于源码被泄露
解决:
1.开发环境下将dev-tool设置成cheap-module-eval-source-map,因为生成这种Source Map的速度最快,能加速构建,
在开发环境下不会做代码压缩,所以在Source Map中即使没有列信息,也不会影响断点调试
2.生产环境下将dev-tool设置成hidden-source-map,生成最详细的Source Map,但不会将Source Map暴露出去
在生产环境会做代码压缩,一个JS文件只有一行,所以需要列信息

生产环境通常不会将Source Map上传到HTTP服务器让用户获取,而是上传到JS错误错误收集系统,
在错误收集系统上根据Source Map和收集到的JS运行错误堆栈,计算出错误所在源码位置

第三方模块

webpack是默认不会加载第三方模块附带的Source Map文件的,会在转换过程中生成Source Map,
为了让webpack加载这些第三方模块的Source Map,需要使用source-map-loader

1
2
3
4
5
6
7
8
9
rules:[{
test:/\.js$/,
include:[path.resolve(root,'node_module/some-component')],
//加载Source Map时计算量很大,因此要避免让该Loader处理过多的文件,不然会导致构建变慢
use:[source-map-loader],
//要将source-map-loader的执行顺序放到最前面,
//如果在source-map-loader之前有Loader转换了该JS文件,就会导致Source Map映射错误
enforce:'pre'
}]

watch

是否开启监听文件变动模式

watchOption

配置监听规则,在开启监听模式时,才有用

ignore:不监听的文件/文件夹
aggregateTimeout:监听到变化发生后,等多少ms再去执行动作,截流,防止文件更新太快而导致重新编译的频率太快,默认为300ms
poll:判断文件是否发生变化是通过不停的询问系统指定文件有没有变化实现的,这里配置每秒询问多少次

文件监听原理:
默认情况下,webpack会从配置的Entry文件出发,递归出Entry文件依赖的文件,将这些依赖的文件都加入到监听列表中,而不是监听整个项目目录下的文件
然后对列表中每个文件都定时执行检查,定时获取这个文件的最后编辑时间,每次都存下最新的最后编辑时间,如果发现当前获取的和最后编辑时间不一致,
就认为该文件发生了变化,watchOption.poll控制定时检查的周期
文件发生变化后并不会立刻告诉监听者,而是先缓存起来,收集一段时间的变化后,再一次性告诉监听者。watchOption.aggregateTimeout用于配置这个等待时间
这样做的目的就是在编辑代码过程可能会高频地输入文字,导致文件变化的时间高频发生,如果每次都重新执行构建,就会让构建卡死

externals

配置哪些模块不用被打包

resolveloader

配置如何去寻找loader

My Little World

多请求loading处理

发表于 2019-03-03

背景

项目中所有http请求走同一个全局公共函数,函数参数为:请求方法,url, 请求参数,回调函数以及是否需要加loading的一个布尔值

一般情况下都需要加loading,所以第5个参数一般不填,默认为true,需要加loading
不加loading的情况也有,例如下载一个文件的过程,如果是由前端来组装文件流,这个时候是不加loading的

loading效果的添加是通过jquery判断有没有loading效果对应的DOM,有的话就先remove再append
loading效果的消失通过jquery remove掉相关DOM元素

即在全局公共函数发出http请求前,根据第5个参数,添加loading,请求结束后取消loading

问题

一个配置页面,跳转过来时需要加载多项数据进行初始化,而且数据之间有一定关联
比如,异步请求A,B,C,D四个数据,请求完D之后,在回调函数中,需要根据D数据发出请求E,再根据E返回数据请求F
导致的现象是,ABCD数据回来比较快,然后loading消失,发出E时loading重现,消失和重现时间距离较短,
造成页面闪烁

目标

加载ABCDEF请求loading仅显示一次,消失一次

分析

1.其实本身发出ABCD四个异步请求时也会发生loading问题,即,如果A请求回来的比其他三个早很多,loading会提前结束
2.从D返回的数据可能为空这时就不需要去请求E,F请求也就不用发出,同样如果E没数据,也就不要请求F,这时候loading在哪结束需要判断

解决

1.在加载loading和消失loading时,添加一层计数,加载loading,计数count加1,消失loading计数count减1,当count减为0时,loading再消失
这样就可以解决异步请求loading提前消失
2.D请求完才会发出E请求,这时如果ABC请求早已结束,count已经被减为0 ,会重新开始计数,loading会重现造成闪烁,还是不能解决直接问题,
解决办法就是,在请求回来时,利用settimeout包裹计数减1,loading消失过程,先执行回调函数再减1,
这样,如果D有数据,count会因为要发请求E先加1,然后执行settimeout减1,这样loading就不会因为减到1而消失
如果 D没数据不发出请求E,同步执行完回调函数后,执行settimeout减1,count减到0,loading消失

后续

回调函数中,有同事代码编写存在BUG,比如没有判断对象是否存在直接去使用其身上的属性值,这样的error会在请求的catch中被捕捉到,
因为请求发生错误,状态码非200都会被捕捉到catch中,这时也需要将loading关闭,
这样就会造成在回调函数发生错误时,count被减了两次,第一次是请求正常回来时,第二次是回调函数发生错误在catch中被减1,
这样count就被减成了小于0,之前的判断以及count等于0,loading消失不再适用,
因此将判断界限定为小于1即loading消失,进行容错处理

My Little World

关于CSS3样式的一点小结

发表于 2019-02-21

border-image

会覆盖border属性设置的边框样式
原理依据border-image-slice将图片切成九宫格
borderOri
EFGH位于边框四角,不参与拉伸或重复
ABCD分别在各自所在边上进行拉伸或者重复
I不参加修饰

border-image-source

图片地址
格式url(imgurl)

border-image-slice

从距离top,right,bottom,left边多少切割图片,切4刀,形成9宫格
可为数字(图像的像素(位图图像)或向量的坐标(如果图像是矢量图像))/百分数(水平/垂直偏移图像宽度的百分之多少)
不设置时,图片整体位于边框四角。
一个值时表示4个距离一致;两个值表示垂直方向两刀和水平两刀距离相同,四个值表示距离四个边各自的距离
特殊情况,
如果距上下边的长度等于高的一半,即在图片水平中线来一刀,则BD两个切片不会被切出来,左右边框会因为没有切片覆盖显示background-color
同理,距左右边的长度等于宽的一半,在图片垂直中线来一刀,则AC两个切片不会被切出来,上下边框会因为没有切片覆盖显示background-color
所以如果仅装饰边框4个角,可以采用距离边等于宽高一半的方法切割图片

border-image-repeat

水平边要不要repeat 垂直边要不要repeat 默认拉伸
可取值:stretch 默认拉伸;repeat 重复切片;round;重复切片,但会适当调整大小,避免出现半截图片
borderimg
borderimg1

border-radius

设置边框外圆半径
5px ———— 4个角都设半径为5px的圆形圆角
5px/10px ———— 4个角都设水平半径为5px,垂直半径为10px的椭圆形圆角
5px 10px 左上和右下角半径5px圆形,右上和左下半径10px的圆形圆角
5px 10px 20px 左上角半径5px圆形,右上和左下半径10px的圆形,右下角半径20px圆形圆角
5px 10px 20px 15px 左上角半径5px圆形,右上半径10px的圆形,右下角半径20px圆形,左下15p圆形圆角
5px 10px 20px 15px/10px 15px 25px 20px 四个角同时设置椭圆型圆角

当半径值小于等于边框border宽度时,border内部不会具有圆角效果,内圆半径=外圆半径-边框宽度
若相邻边宽不同,则角会从宽边平滑到窄边
表格table设置圆角时,只有将border-collapse:separate时才正常显示
如果想取消原有圆角设置,可以将对应值设置为0
特殊应用:直接通过设置容器长宽和border-radius绘制圆形,半圆,扇形,椭圆

1
2
3
4
5
6
7
8
9
width:300px
height:300px
border-radius:150px;//圆形
border-radius: 0 300px 0px 0px;//扇形

width:600px
height:300px
border-radius:150px;//椭圆形
border-radius: 300px 300px 0px 0px;//半圆

box-sizing

content-box ———— 元素width代表内容宽度
border-box ———— 元素width = border+padding + 内容宽度

resize

容器能否拖拽以及拖拽方式
none | both | horizontal | vertial | inherit

outline

相当于border,但不占文档流,不破坏布局
颜色 样式 宽度 偏移

box-shadow

边框阴影,由多个值配置
insert ? 水平方向偏移(正右负左)垂直偏移(正下负上)模糊半径 伸缩半径(相当于阴影宽度)
设置insert时,设置内阴影,不设时,设置外阴影
内阴影用在img标签上无效
实例应用
1.单边阴影
box-shadow:red 0px -5px 5px -3px,给top边设置了阴影,通过设置伸缩半径-3px防止其他边模糊半径5px效果
2.四边阴影
box-shadow:0 0 10px red;//10px为模糊半径
box-shadow:0 0 0 10px red;//10px为伸缩半径,此效果相当于设置了一个宽10px的红边,但不占文档流
层级关系:外阴影—>背景色—>背景图—>内阴影—>边框
3.多层阴影
每组用逗号隔开,靠前的设置面积(伸缩半径)太大的话会遮盖之后的设置

1
2
//彩虹色
box-shadow: red 0px 0px 10px 10px, inset orange 0px 0px 10px 15px, inset yellow 0px 0px 10px 30px, inset green 0px 0px 10px 45px, inset blue 0px 0px 10px 60px, inset purple 0px 0px 10px 75px;

text-shadow

水平方向偏移(正右负左) 垂直偏移(正下负上) 模糊半径 颜色(位置随意)

text-overflow

超出容器是否显示省略号
使用条件
width:500px;容器有具体宽度
overflow: hidden;超出隐藏
white-space: nowrap;//禁止换行

word-wrap

长单词url换行
normal 默认,半角空格/连字符地方换行,长文本UrL会伸到容器外
break-word 在边界换行,不截断英文单词

word-break

自动换行处理方法
normal 默认,整字,英文单词整个换行
break-all 可截断单词换行
keep-all 不允许断开换行,chrome,safari不支持

white-space

页面显示与html文本书写格式处理
normal 根据容器宽度换行,连续空格合并成一个
pre 空白换行跟html书写格式一致,行为类似pre标签
nowrap 不换行,连续空格合并成一个,超出容器尺寸按容器overflow处理
pre-line 换行跟html书写格式一致,续空格合并成一个
pre-wrap 保留空白,正常换行,与pre区别在于pre标签对一些符号上的支持和规范

文本换行

1.pre标签自动换行

1
2
3
4
pre{
white-space:pre;
word-wrap:break-word
}

2.td自动换行

1
2
3
4
5
6
7
8
table{
table-layout:fixed;
width:***px;
}
table td{
overflow:hidden;
word-wrap:break-word;
}

3.除以上两种标签自动换行

1
2
3
4
element{
overflow:hidden;
word-wrap:break-word;
}

4.标签内容强制不换行

1
2
3
4
element{
white-space:nowrap;
word-break:keep-all;
}

background-attachment

背景图是否随页面滚动条滚动,始终不会随容器滚动条滚动
scroll ———— 滚动
fixed ———— 不滚动

background-image

1.起始位置
background-color始终从border与margin交界线的左上角(用A表示)开始
background-image默认从padding与border交界线的左上角(用B表示)开始,但是可以通过设置background-position决定起始点
2.在Z轴上,覆盖顺序
background-color ——> background-image ——> border,conten的背景色
background-repeat值为repeat和no-repeat时,对覆盖有不同影响

background-repeat

图片比容器小时,重复方式
no-repeat ———— 仅从B点开始铺一次,background-image不会填充border部分的background-color,图片不够大时会显示background-color
bgimg2
repeat ———— 从B点开始,先垂直重复,再整体水平重复,background-image会覆盖border部分的background-color,图片起点还是B,然后垂直重复,整体水平重复
bgimg1
repeat-x ———— 从B点开始,仅水平重复,不再整体垂直重复,覆盖border
repeat-y ———— 从B点开始,仅垂直重复,不再整体水平重复,覆盖border

background-position

背景图片开始位置在padding与border交界线什么地方
两个值为长度或者百分数时,第一个值表示容器水平方向的位置,相对于left边位置;第二个值表示容器垂直方向的位置,相对于TOP边位置,可以混用
但只设一个值时,表示距离left位置,垂直方向默认为居中
两个值为关键字时,left,right,center表示水平位置,top,bottom,center表示垂直位置,
前后位置可随意,left bottom 等于 bottom left
仅设置一个值时,另一个方向默认center,例left 相当于left center,背景图位于左边垂直中间位置;center,即水平垂直居中
bgimg3

background-origin

同样可以重置图片开始位置,指定居于哪条线,
同时指定background-position时,先根据background-origin确定线,依线根据background-position确定位置
border-box ———— margin与border交界线
padding-box ———— border与padding交界线,默认值
content-box ———— padding与内容块交界线

background-clip

设置背景色background-color铺设范围
border-box ———— 从margin与border交界线开始铺
padding-box ———— 从border与padding交界线开始铺
content-box ———— 从padding与内容块交界线开始铺

background-size

以background-origin设置的值为容器,设置图片大小
auto 原始尺寸
具体长度值1个或2个
百分数1个或2个 相对容器的百分之多少,不是图片大小
contain 放大,铺满容器
cover 按照图片宽高比例,缩放

opacity

透明度:0-1的值,值越小越透明
表示颜色的rgba,hsla的第四位都表示透明度,取值同opacity

linear-gradient

线性渐变,用于background-image
到达方向,开始颜色,(中间过渡色,)结束颜色
到达方向:to left 从右到左;to top left 从右下到左上角;xx deg,使用角度正值顺时针,负值逆时针
颜色名后空格加百分数,百分数指色标,色标即颜色在整个距离上开始的位置

radical-gradient

径向渐变,用于background-image
半径 (形状)(at 圆心位置),开始颜色,(中间过渡色,)结束颜色
半径:两值相同即为圆形渐变,否则为椭圆渐变;
距离容器中心 closed-side 最近边| closed-corner 最近角| farthest-side 最远边| farthest-corner最远角距离
形状: circle | ellipse
圆心位置:left bottom |100px 200px |20% 15% 默认center

transform

transform-origin

重置变形函数作用起点,但translate()移动函数始终以元素中心点进行位移
left bottom | 20% 10%

transform-style

子元素以2D还是3D形式处理变换过程
flat :2D
present-3d: 3D
如果同时有设置overflow:hidden,那么设置present-3d不生效,相当于flat

perspective

父元素是否呈现3d效果,这越小3D效果越明显,none不显示

perspective-origin

perspective 原点,从哪个点开始呈现3d效果

perspective()

用于子元素,参数大于0时,激活3d效果

back-visibility

旋转180度,背面是否显示为正面的镜面效果,visible | hidden
3D表示是否透视

功能函数

可在transform中空格隔开同时使用多个
2D
位移
translate(_ |X,Y) X正值为向右,Y正值为向下
translateX()
translateY(
)
放缩
scale(_ |X,Y) 0-1缩小,大于1放大,取负值时,先翻转再放缩
scaleX()
scaleY(
)
旋转
rotate()角度值,顺时针正,逆时针负
倾斜
skew(
|X,Y) 水平 垂直方向倾斜
skewX()
skewY(
)

3D
位移
translate3d(X,Y,Z) X正值为向右,Y正值为向下
translateZ()
放缩
scale3d(
|X,Y,Z) 0-1缩小,大于1放大,取负值时,先翻转再放缩
scaleZ()
旋转
rotate3d(X,Y,Z,角度)X,Y,Z值为0-1,表示绕轴旋转的矢量值,角度值,顺时针正,逆时针负
rotateX(
)
rotateY()
rotateZ(
)

以上功能都能让矩阵函数表示
2d时使用matrix()
3d时使用matrix3d()

transition

过度属性(css样式)none/all,具备过渡效果的属性,color,阴影,渐变
过渡时间 s/ms 整个变化持续时间
过度函数 ease|linear|ease-in|ease-out|ease-in-out|step,以上函数表示以什么样的速度变化,都可以用三次贝塞尔曲线实现
延迟时间 s/ms 变化延迟多长时间再开始,赋值为负值时,立即开始,之前的变换被截断
配置多项时,每组配置逗号隔开
终状态一般定义于各种触发伪类:hover,active,focus,checked
或者根据媒体查询结果

@keyframes

定义动画每一帧样式
@keyframes 取个名字apple{
0%{动画第一帧样式}
各种百分数{动画过渡帧样式}
100%{动画最后一帧样式}
}

animation

animation各个子属性
animation-name:@keyframes声明的动画帧的名字,例如apple
animation-duration:播放一遍的时间
animation-timing-function:播放方式
animation-delay:延迟多长时间再播放
animation-iteration-count:播放次数
animation-direction:倒放还是正放动画normal正放alternate倒放
animation-play-state:播放状态running播放|paused暂停
animation-fill-mode:结束定格在哪一帧

@media

媒体查询
@media 10种媒体类型 and (13种设备特性) and (13种设备特性){样式}

@font-face

从服务器加载字体类型

1
2
3
4
@font-face{
font=family:xxx;
src:url()
}
My Little World

探索flex布局

发表于 2019-02-01

flex布局
盒状弹性布局,盒子大小可以随容器大小变化

容器属性

display 决定开始作为弹性容器

—— flex : 将容器设置为弹性伸缩容器
—— inline-flex :使弹性容器成为单个不可分的行内级元素
注意,columns属性此时在flex容器上不在有效果

flex-direction 决定主轴方向
—— row:水平向右,子模块书写顺序横向排列,开头的放在最左端,子模块不设宽度情况下宽度由内容决定
—— row-reverse:水平向左,子模块书写顺序横向排列,开头的放在最右端,子模块不设宽度情况下宽度由内容决定
—— column:从上到下,子模块书写顺序纵向排列,开头的放在最上面,子模块不设高度情况下高度由内容决定
—— column-reverse:从下到上,子模块书写顺序纵向排列,开头的放在最下面,子模块不设高度情况下高度由内容决定
flex1

flex-wrap 对于子模块是否超出最大宽度或高度时要不要换行/列,默认不换行/列,宽/高度自动按所有模块比例缩小
—— nowrap:默认值
—— wrap:换行,水平排列的子模块多出的部分沿column方向排第二行;纵向排列的子模块多出的部分沿row方向排第二列;
—— wrap-reverse:换行,水平排列的子模块多出的部分沿column-reverse方向排第二行;纵向排列的子模块多出的部分沿row-reverse方向排第二列;
flex2.png
flex3.png
flex4.png

flex-flow :[flex-direction][flex-wrap]flex-direction和 flex-wrap的复合属性,
先按flex-direction排列,超出部分用flex-wrap决定
flex5.png

justify-content 在剩余子模块允许换行或者有剩余宽度或者高度时相对主轴的对齐方式,超出但不换行时,按比例放缩
—— center:按实际间距位于主轴中心
—— flex-start:按实际间距位于主轴开始排列的位置,第一个贴边
—— flex-end :按实际间距和顺序向主轴方向排列,最后一个贴边
—— space-between :第一个和最后一个挨着边框,其它间距相等
—— space-around:第一个和最后一个离边框距离为子模块间距的一半
处理主轴为侧轴的布局时,设置了具体高度才会生效
flex6.png

align-items 子模块整体相对侧轴的位置,row和row-reverse侧轴为column,column和column-reverse侧轴为row
—— center:侧轴中心
—— flex-start:侧轴起点位置
—— flex-end :侧轴终点位置
—— stretch:侧轴方向属性值未设置时,拉伸子模块该属性为容器对应属性值,弹性元素被在侧轴方向被拉伸到与容器相同的高度或宽度。
—— baseline:所有模块的内容第一行对齐
flex7.png

align-content 决定多行或者多列在侧轴排列方式,row和row-reverse侧轴为column,column和column-reverse侧轴为row
—— center:整体在侧轴中心
—— flex-start:整体在侧轴起点位置
—— flex-end :整体在侧轴终点位置
—— stretch:侧轴方向属性值未设置时,拉伸子模块该属性填满容器对应属性值
—— space-between :第一行/列和最后一行/列挨着边框,其它间距相等
—— space-around:第一行/列和最后一行/列离边框距离为子模块间距的一半
flex8.png

模块属性

align-self 自己决定相对侧轴的位置
属性值同align-items,默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

order 决定模块排列顺序,值越大,越最后被排列

flex-grow 放大比例,当容器空间有剩余时按此比列分配内存空间,填满,比如
宽度1000px的容器,4个子模块一共占了800,剩200,4个模块该值分别设为2 :1:1:1,
则200被分为5份,一份40,每个模块按比例 分别拿80,40,40,40 放大自己的面积

flex-shrink 缩小比例,当容器空间有不足时按此比列分配缩小子模块,比如
容器宽800,5个模块需要占1000,差200,5个模块该值比为4:1:1:1:1,
则200被分为8份,一份25,,每个模块按比例分别减少100,25,25,25,25 缩小自己的面积

flex-basis 在分配多余空间/缩小之前,项目占据的主轴空间,用于计算一共需要多少空间时放大还是缩小
auto 默认值,子模块本来大小

flex :none | [ <’flex-grow’> <’flex-shrink’>? || <’flex-basis’> ] 以上三项复合属性

一些特殊flex 值的表现情况
flex:auto | 1 1 auto; 空间充裕时,模块等比例分配多余空间进行扩展;空间不足时,模块等比例缩小
flex:none | 0 0 auto; 不论空间是否充裕,模块大小根据自身设置的尺寸不变,间距变化
flex:initial | 0 1 auto | 0 auto; 空间即使充裕也保持原始大小,但空间不足时,模块等比例缩小
flex:正值; 模块可根据空间大小收缩,同时多个模块设置时,以该值作为比例进行收缩,放大比等于缩小比

注意,float,clear和vertical-align在伸缩项目上没有效果
flex 实现 圣杯布局

My Little World

Slot复用

发表于 2019-01-13

背景

项目列表组件需要实现点击自动增加一行,可展示自定义内容的可扩展内容
列表table组件被应用在page组件,page组件被应用在具体业务页面,slot的具体内容在业务页面传递进去
tableextend1
问题难点
1.多层组件slot传递
2.同一个slot在table组件中循环使用在多处时,不会被渲染

原理

1.slot 只能一层一层传递,所以解决多层传递,就是在page层应用table时,增加slot接收业务页面传递进来的slot具体内容
2.同名slot不能在同一组件被重复渲染,即不允许有重名slot

1
2
重名的 Slots 移除
同一模板中的重名 <slot> 已经弃用。当一个 slot 已经被渲染过了,那么就不能在同一模板其它地方被再次渲染了。如果要在不同位置渲染同一内容,可一用 prop 来传递。

回归问题

多层组件slot传递

在page层使用table组件时将<slot></slot>放在table组件中,将slot内容当作table的slot传递给table

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
1.业务页面具体自定义内容传入
<PageComponent :pageConfig="pageConfig" ref="child">
<div slot='tableExtend'>
<extendTable :detailConfig="pageConfig.table.isExtend.detailConfig"
v-if="pageConfig.table.isExtend.detailConfig[0].data.length>0">
</extendTable>
<div style="padding: 10px;" v-if="pageConfig.table.isExtend.detailConfig[0].data.length === 0">暂无数据</div>
</div>
</PageComponent>

2.page组件table部分
<section class="page-table">
<tableTemp :table="pageConfig.table" :pageConfig="pageConfig" @sendIds="receiveIds" ref="refTest">
<slot :name='pageConfig.table.isExtend.slot' v-if="pageConfig.table.isExtend"></slot> //将slot放在这里
</tableTemp>
</section>
3.table部分
利用template实现列表行和扩展行一对一
<template v-for="(value,index) in table.tableData">
<tr :key="index">
<td v-for="(val,i) in table.tableEle" v-if="val.display" :key="i">
<div class="extendStyle" v-if='table.isExtend && i==firstShow'>
<a v-show="index != currentActive" @click="loadDetail(value,index)"><i class="ivu-icon ivu-icon-ios-arrow-forward"></i></a> //loadDetail去调父组件函数获取详情数据
<a v-show="index === currentActive" @click="loadDetail(value,index)" class="active"><i class="ivu-icon ivu-icon-ios-arrow-forward"></i></a>
</div>
<div v-if='!table.isExtend'>
...
</div>
</td>
</tr>
//在组装行的时候增加备用扩展行,利用第三方变量控制扩展行显示隐藏
<tr v-if="index === currentActive" :key="index+0.5" class='bgc'>
<td :colspan='tdNumber' id='extend1'>//合并单元格
<slot :name='table.isExtend.slot'></slot>//直接这样是不能渲染的,因为这样通过循环会出现多个同名slot
</td>
</tr>
</template>
//去调父组件函数获取详情数据
loadDetail(item, index) {
this.currentActive = this.currentActive === index ? -1 : index
this.$store.commit('changeTableExtendActive',this.currentActive)
if(this.currentActive === -1){
return
}
let func = this.pageConfig.table.isExtend.func
this.$parent.$parent[func](item,index)
},

slot重用

方法一 作用域插槽

在子组件中将slot中用到的数据传递给slot标签的data属性,
父组件借助slot-scope属性,获取子组件中slot标签的data属性传递的数据
即可应用在slot模板中,另外slot模板中如果用到点击事件回调函数,可以直接在父组件中定义,直接调用

注意slot-scope绑定的变量在使用时,子组件传递的实际值被包含在一个data属性中,所以需要通过slotscope.data.xxxxx去获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
爷爷组件
<PageComponent :pageConfig="pageConfig" ref="child">
<template slot-scope="isExtend">
<extendTable :detailConfig="isExtend.data.detailConfig" //包含的data中
v-if="isExtend.data.detailConfig[0].data.length>0">
</extendTable>
<div style="padding: 10px;" v-if="isExtend.data.detailConfig[0].data.length === 0">暂无数据</div>
</template>
</PageComponent>
父组件
<section class="page-table">
<tableTemp :table="pageConfig.table" :pageConfig="pageConfig" @sendIds="receiveIds" ref="refTest">
<slot :data='pageConfig.table.isExtend' v-if="pageConfig.table.isExtend"></slot>
</tableTemp>
</section>
子组件
<tr v-if="index === currentActive" :key="index+0.5" class='bgc'>
<td :colspan='tdNumber' id='extend1'>
<slot :data='table.isExtend'></slot> //直接使用
</td>
</tr>

插曲

一个业务场景需要同时渲染多个table,即通过一次配置,可渲染多个table的组件,
一个同事将循环table的逻辑放在了table组件本身里面,造成table本身是一个可循环输出多个table的组件
这样如果一开始在爷爷组件放入多个具名slot,因为不能在子组件slot标签添加name属性(会循环出同名slot),
那所有的slot都会出现在每一个table组件中
因此有了方法二
tableextend2

方法二

利用page层传递slot到table组件思想,在table中使用slot时,将其包裹在一个新组件中,
利用新组件复用,实现slot组件复用,(相当于将slot传递到新组件——->待查原理)
而新组件通过render函数依据slot节点生成

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
子组件
<tr v-if="(indexx+'-'+index) === currentActive" :key="index+0.5" class='bgc'>
<td :colspan='itemConfig.config.length'>
<!-- slot标签要放在tdslot标签充当slot,否则详情数据拉回来之后不会进行更新组件 -->
<tdSlot :name='itemConfig.isExtend.slot'> <slot :name='itemConfig.isExtend.slot'></slot> </tdSlot>
<!-- tdslot在main.js中定义 -->
</td>
</tr>
loadDetail(item, indexx, index) {
this.currentActive = this.currentActive === indexx+'-'+index ? -1 : indexx+'-'+index //通过多层key判断展开
if(this.currentActive === -1){
return
}
let func = this.detailConfig[indexx].isExtend.func
this.$parent.$parent[func](item,indexx, index)
}
新组件带着slot生成
Vue.component('tdSlot', {

render(createElement) {
function deepClone(vnodes, createElement) {
function cloneVNode (vnode) {对slot节点进行深度复制
const clonedChildren = vnode.children && vnode.children.map(vnode => cloneVNode(vnode))
const cloned = createElement(vnode.tag, vnode.data, clonedChildren)
cloned.text = vnode.text
cloned.isComment = vnode.isComment
cloned.componentOptions = vnode.componentOptions
cloned.elm = vnode.elm
cloned.context = vnode.context
cloned.ns = vnode.ns
cloned.isStatic = vnode.isStatic
cloned.key = vnode.key

return cloned
}
const clonedVNodes = vnodes.map(vnode => cloneVNode(vnode))
return clonedVNodes
}
var slots = this.$parent.$slots.default //从父组件拿到slot
var slot = null
for(let i=0;i<slots.length;i++){
if(slots[i].data && this.name === slots[i].data.slot){ //多个slot,拿到自己table.isExtend配置的那一个
slot = slots[i]
break
}
}
return createElement('div',{class:'tdslot'},deepClone([slot], createElement)) //做一下深度复制
<!-- return createElement('div',{class:'tdslot'},[slot]) --> 这样也可以
},
props:{
name:{
type:String,
default:''
}
}
})

父组件
<detailsTable v-if="detailPageConfig.detailConfig" :detailConfig="detailPageConfig.detailConfig">
<slot v-for='(item,index) in detailPageConfig.detailConfig' v-if='item.isExtend' :name='detailPageConfig.detailConfig[index].isExtend.slot' ></slot>
</detailsTable>
爷爷组件
<detailsPage :detailPageConfig="detailPageConfig">
<div slot='tableExtend-1'> //多个slot 并列写即可
<detailsTable :detailConfig="detailPageConfig.detailConfig[1].isExtend.detailConfig"
v-if="this.detailPageConfig.detailConfig[1].isExtend.detailConfig[0].data.length>0">
</detailsTable>
<div v-if="this.detailPageConfig.detailConfig[1].isExtend.detailConfig[0].data.length<1">暂无数据</div>
</div>
</detailsPage>

方法三

其实之所以会出现重名slot是因为table的大循环逻辑放在了自己组件内,
其实输出多个table的逻辑放在page层即可,table还是独立的table,
这样插入slot时就不是同名slot,各自slot,插入各自table

1
2
3
4
5
6
7
8
9
10
11
12
父组件
<template v-for='(item,index) in detailPageConfig.detailConfig'>
<detailsTable :detailConfig="item" :key='index'>
<slot v-if='item.isExtend' :name='item.isExtend.slot' ></slot>
</detailsTable>
</template>
子组件
<tr v-if="index === currentActive" :key="index+0.5" class='bgc'>
<td :colspan='detailConfig.config.length'>
<slot></slot>//只要将自己的slot放进来即可
</td>
</tr>

参考资料
参考资料
参考资料

1…8910…25
YooHannah

YooHannah

246 日志
1 分类
21 标签
RSS
© 2025 YooHannah
由 Hexo 强力驱动
主题 - NexT.Pisces