1.Typescript中的&理解
constructor(props: TagManagementStateProps & TagManagementOwnProps & TagManagementDispatchProps)
参数中的 & 表示props对象同时拥有了TagManagementStateProps、TagManagementOwnProps、TagManagementDispatchProps这三种类型的成员。交叉类型:将多个类型合并为一个类型,这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
2.takeEvery
export default function* () {
yield takeEvery(TAG_LISTEN_QUERY, queryListSaga);
yield takeEvery(TAG_LISTEN_QUERY_INIT_DATA, queryInitDataSaga);
yield takeLatest(TAG_LISTEN_FETCH_MATERIAL_TAGS_SAGA, queryMaterialTagsSaga);
yield takeEvery(TAG_LISTEN_SAVE_QUESTION_TAGS, saveTagsSaga);
}
takeEvery:允许多个 fetchData 实例同时启动。在某个特定时刻,我 们 可以启动一个新的 fetchData 任务, 尽管之前还有一个或多个 fetchData 尚未结束
takeLatest:只允许执行一个 fetchData 任务,并且这个任务是最后被启动的那个,如果之前已经有一个任务在执行,那之前的这个任务会自动被取消。
3.由function*定义的generator(生成器)
generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。 generator和函数不同的是,generator由function定义(注意多出的号),并且,除了return语句,还可以用yield返回多次。
4.encodeURI、encodeURIComponent、decodeURI、decodeURIComponent
-
这四个方法的用处
1、用来编码和解码URI的
统一资源标识符,或叫做 URI,是用来标识互联网上的资源(例如,网页或文件)和怎样访问这些资源的传输协议(例如,HTTP 或 FTP)的字符串。除了encodeURI、encodeURIComponent、decodeURI、decodeURIComponent四个用来编码和解码 URI 的函数之外 ECMAScript 语言自身不提供任何使用 URL 的支持。2、URI组成形式
一个 URI 是由组件分隔符分割的组件序列组成。其一般形式是:
Scheme : First / Second ; Third ? Fourth
其中斜体的名字代表组件;“:”, “/”, “;”,“?”是当作分隔符的保留字符。3、有和不同?
encodeURI 和 decodeURI 函数操作的是完整的 URI;这俩函数假定 URI 中的任何保留字符都有特殊意义,所有不会编码它们。
encodeURIComponent 和 decodeURIComponent 函数操作的是组成 URI 的个别组件;这俩函数假定任何保留字符都代表普通文本,所以必须编码它们,所以它们(保留字符)出现在一个完整 URI 的组件里面时不会被解释成保留字符了。
以上说明摘自ECMAScript标准,为了容易读懂做了点编辑加工。
4、图解四个函数的不同:
当 URI 里包含一个没在上面列出的字符或有时不想让给定的保留字符有特殊意义,那么必须编码这个字符。字符被转换成 UTF-8 编码,首先从 UTF-16 转换成相应的代码点值的替代。然后返回的字节序列转换为一个字符串,每个字节用一个“%xx”形式的转移序列表示。(具体转换规则可以参考抽象操作Encode和Decode的说明)
5.toUTCString
可根据世界时(UTC)把Data对象转换为字符串,并返回结果
6.Infinity:无穷大
7.@keyframes:创建动画(一般要注意浏览器的兼容性)
@keyframes animationname {keyframes-selector {css-styles;}}
animationname:声明动画的名称。
keyframes-selector:用来划分动画的时长,可以使用百分比形式,也可以使用 "from"和 "to"的形式。"from" 和 "to"的形式等价于 0% 和 100%建议始终使用百分比形式。
8.git clone时,报403错误
刚开始以为是项目组那边没有授权,但感觉又不对,找了一下发现是git 客户端缓存了错误的密码,解决方法:
1、增加远程地址的时候带上密码。(推荐)
git clone https://zhuyan.luo:[welcome123@git.zhishinet.com](mailto:welcome123@git.zhishinet.com)/zhishinet/TeacherClient/
2、运行命令:rm ~/.git-credentials,删掉git config --global credential.helper store保存的账号和密码。回到每次输入用户名和密码。
~/.git-credentials
9.Object.assign()
[FROM_DRAFT_TO_PERSON]: (state, action: Action<any>) => {
return Object.assign({},state,{
fromDraftsToPersonFlag: action.payload
})
},
Object.assign()用于把一个或多个源对象的可枚举属性复制到目标对象中,返回值为目标对象
10.JavaScript中的可枚举属性与不可枚举属性
在JavaScript中,对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。可枚举性决定了这个属性能否被for…in查找遍历到。js中基本包装类型的原型属性是不可枚举的,如Object, Array, Number等,
11.count = (count + '') === '0' ? '0' : count; (count是id)
这样做可避免 !1=false,!0=true (!'0'=false)
12.Store
Store 是保存数据的地方,可以把它看成一个容器,整个应用只能有一个 Store。Redux 提供createStore这个函数来生成 Store。
import { createStore } from 'redux';
const store = createStore(fn);
上面代码中,createStore函数接受另一个函数作为参数,返回新生成的 Store 对象。
13.State
Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
当前时刻的 State,可以通过store.getState()拿到。
14.box-shadow 阴影
用法:box-shadow: 水平阴影的位置(可为负值) 垂直阴影的位置(可为负值) [模糊距离] [阴影的尺寸] [阴影的颜色] [阴影类型 (outset、inset)];
实现鼠标hover时延迟出现阴影(结合transition-duration、transition-property两个属性使用)
#edit_card {
width: 100%;
height: auto;
transition-duration: .8s;
transition-property: box-shadow;
&:hover{
box-shadow: 0 0 12px rgb(153,153,153);
}
}
15.多重边框实现
1、 outline实现
不足之处:
2、 box-shadow实现
16.在入口文件app.tsx中引入了react-hot-loader中的AppContainer组件,这个组件下的所有子组件都会在发生变化时触发热更新
17. 在Typescript中Table组件使用fixed属性报错
解决方法:https://github.com/ant-design/ant-design/issues/3772#issuecomment-259358384
①定义columns时TableColumnConfig给any的类型:columns: TableColumnConfig<any>[] = [...]
②给列定义fixed属性时写成:fixed: 'right' as 'right'
③或者写成下面这样
import React, { Component } from 'react';
import { Table } from 'antd';
import { TableColumnConfig } from 'antd/lib/table/Table';
interface Person {
name: string;
}
const columns: TableColumnConfig<Person>[] = [{
title: 'Name',
dataIndex: 'name',
fixed: 'right',
}];
const data: Person[] = [{
name: 'Jack',
}];
class PersonTable extends Table<Person> {}
class App extends Component<null, null> {
render() {
return (
<PersonTable columns={columns} dataSource={data} />
)
}
}
18.antd的Select组件,普通的在输入框后面都带有向下的箭头,并且不可输入,加上 mode="combobox" 属性后,下拉框就像一个输入框,既可以输入又能选择;加上showSearch optionFilterProp="children" 后可过滤搜索
19.antd在给组件添加默认值时,不在Form组件中直接使用 defaultValue 就行,但在Form表单中需要写成:initialValue
<FormItem {...formItemLayout} label='保单编号' style={{marginBottom:'0'}}>
{getFieldDecorator('policyNumber',{
initialValue:fields.policyNumber?fields.policyNumber:''
})(
<Input disabled="disabled"/>
)}
</FormItem>
20.table排序:
本地排序使用一个函数(参考 Array.sort 的 compareFunction),需要服务端排序可设为 true
21.新增的时候要传空对象过去,否则先点击编辑再点击新增,新增界面会和编辑界面一样(对象会被缓存)
const handleAdd = () => {
debugger;
onShowModal({
modalType: 'create',
currentItem: {},
});
}
const handleEdit = (item: any) => {
debugger;
onShowModal({
modalType: 'update',
currentItem: item,
});
};
22.Moment.js
日期转时间戳:Date.parse(this.props.form.getFieldValue('startDate')[0])
时间戳转日期:moment.unix(text).format('YYYY-MM-DD HH:mm')
23.需要全局使用的数据在 根RootContainer中获取
24.对于一个查询最好定义四个action
//获取排课表信息周\月数据量
export const COUNT_QUERY = 'scheduleValues/COUNT';
export const COUNT_QUERY_SUCCESS = 'scheduleValues/COUNT_SUCCESS';
export const COUNT_QUERY_ERROR = 'scheduleValues/COUNT_ERROR';
export const COUNT_QUERY_FAILED = 'scheduleValues/COUNT_FAILED';
25.对于请求返回的data的类型不同(对象、数组),要注意yield put(queryCountSuccess(data));,对象传data,数组传data.rows
//获取排课表信息周\月数据量
function* queryCount(action: Action<any>) {
try {
let params = {};
const data = yield request({
url: countUrl,
method: 'get',
data: params,
});
if(data.success){
yield put(queryCountSuccess(data));
} else {
yield put(queryCountError(data.status));
}
} catch (e) {
yield put(queryCountFailed(e.message));
}
}
26.注意下这个 as Array,as Object
[LEVELS_QUERY_ERROR]: (state, action: Action<any>) => {
return Object.assign({}, state, {
levelList: action.payload as Array<any>,
});
},
[LEVELS_QUERY_FAILED]: (state, action: Action<Array<Object>>) => {
return Object.assign({}, state, {
levelList: action.payload as Object,
});
},
27.下拉列表联动
studentSelect(value: any){
let school = null;
for(let i in this.props.studentList){
if(value === this.props.studentList[i].userId ){
school = this.props.studentList[i].school;
break;
}
}
this.props.form.setFieldsValue({school: school}) ;
}
<Select
placeholder="学生/手机"
style={{ width: '100%' }}
onChange={this.studentSelect.bind(this)}
>
{
this.props.studentList !== undefined && this.props.studentList!=="" &&
this.props.studentList.map((item: any) =>
<Option key={item.userId} value={item.userId} >{item.firstName} / {item.cellNumber}</Option>
)
}
</Select>
28.根据接口返回数据的类型调用不同的请求方法,返回是json使用request; 返回字符串或者无返回值的使用autoRefreshTokenFetch,再将返回数据使用 .json()转化成json
//获取活跃人数 (接口只返回了 11)
function* queryActiveCount(action: Action<any>) {
try {
const request = {
method: 'GET',
credentials: 'include',
headers: {
'Authorization': (window as GlobalDefinitions).authorization,
'Accept': 'application/json',
'Content-Type': 'application/json',
'Cache-Control':' no-cache'
},
url: activeCountUrl +'/TEACHER'
};
const data = (yield call(autoRefreshTokenFetch, request));
if(data.ok){
let json = yield data.json();
yield put(queryActiveCountSuccess(json));
} else {
yield put(queryActiveCountError(data.status));
}
} catch (e) {
yield put(queryActiveCountFailed(e.message));
}
}
//获取英语、数学人数(接口返回json {English: 8, Math: 3} )
function* querySubjectCount(action: Action<any>) {
try {
let params = {};
const data = yield request({
url: subjectCountUrl,
method: 'get',
data: params,
});
if(data.success){
yield put(querySubjectCountSuccess(data));
} else {
yield put(querySubjectCountError(data.statusCode));
}
} catch (e) {
yield put(querySubjectCountFailed(e.message));
}
}
29.Select的value只能是String类型的数据,有时候数据库取出来的是Number的,想要利用initialValue初始显示在Form表单中的话需要将Number的转化成String,否则在下拉框中将会直接显示数字
initialValue: item.gender? item.gender.toString():'',
30.在Form表单中,想要使按Enter键的作用和点击某个按钮的作用一样,可以在那个按钮上加 htmlType="submit" 属性
比如在输入框输入完东西后想按enter键进行查询,那么可以在 查询 按钮上加该属性
31.省份provinceList和学校schoolList级联
componentWillMount(){
let provinceList = this.props.provinceList || [];
let schoolList = this.props.schoolList || [];
let options = [];
for(let i in provinceList){
debugger
let temp:any = {};
temp.value = provinceList[i].lookupCode;
temp.label = provinceList[i].lookupValue;
temp.children = [];
for(let j in schoolList){
if(schoolList[j].parentValue == provinceList[i].value){
let subTemp: any = {};
subTemp.value = schoolList[j].lookupCode;
subTemp.label = schoolList[j].lookupValue;
temp.children.push(subTemp);
}
}
options.push(temp);
}
this.setState({
options: options,
})
}
32.正则表达式
/^(?![0-9]+)[0-9A-Za-z]{6,20}) 预测该位置后面不全是数字
(?![a-zA-Z]+ 匹配行结尾位置
33.登录按钮loading状态做法
在点击按钮请求登录接口之前将loading置为true,请求登录接口成功(requestLoginSuccess),在成功那里将loading置为false,请求接口失败同理
34.返回上一页
window.location.reload(); //刷新
window.history.go(1); //前进
window.history.go(-1); //返回+刷新
window.history.forward(); //前进
window.history.back(); //返回
location.href=document.referrer; //document.referrer是获取上一页的url
history.length; //查看历史中的页面数
35.<Link style={{color: '#666'}} to={/CourseManagement/course/schedule
}><Icon type="left"/> 返回</Link>
36.分页组件自定义每页条数pageSizeOptions
pagination: {
...pagination,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: ['10','20','50','100','200','500'],
},
37.Select要求value的类型要为string,将number转化为string的方法为:加个'',但要注意在初始化时也要给初始值加上'',否则显示不出value对应的值,同时后台需要number类型的数据时记得传过去的时候要用parseInt将字符串转成数字
<FormItem {...formItemLayout} label="学生">
{getFieldDecorator('userId', {
initialValue: ''+item.userId,
rules: [
{ required: true, message:'学生不能为空!',},
],
})(
<Select
placeholder="学生/手机"
style={{ width: '100%' }}
onChange={this.studentSelect.bind(this)}
>
{
studentList !== undefined && studentList!=="" &&
studentList.map((item: any, i: any) =>
<Option key={item.userId} value={''+item.userId} >{item.firstName} / {item.cellNumber} {item.school}</Option>
)
}
</Select>
)}
</FormItem>
38.芝士网登录流程
userLogin: `${baseUrl}/api/zauth/v1/token/access`, //登录认证,获取token
refreshToken: `${baseUrl}/api/zauth/v1/token/refresh`, //token有过期时间的,过期了 需要通过 refreshToken 重新拿 token
userInfo: `${baseUrl}/api/HomePageAPI/GetUserInfo`, //传一个userId获取用户信息。(userId是在 知识网 登录页面登录成功后会把userId保存到localStorage里面,现在在我们的工程里没有存userId的过程只有取的过程,在app.tsx中可看到)
39.控制台报错
Cannot read property 'getFieldDecorator' of undefined
是因为使用了Form没有调用Form.create()
40.import一个组件时,如果组件导出时使用了关键字default,则导入时不需将组件用{},若没有default则需要
export const Login = Form.create()(LoginForm);
import {Login} from "./containers/CourseManagement/Login/RootContainer/index";
41.JS在1.6中为Array新增了几个方法map(),filter(),some(),every(),forEach(),也就是一共有这么多方法了。
map():返回一个新的Array,每个元素为调用func的结果
filter():返回一个符合func条件的元素数组
some():返回一个boolean,判断是否有元素是否符合func条件
every():返回一个boolean,判断每个元素是否符合func条件
forEach():没有返回值,只是针对每个元素调用func
42.Promise (https://www.cnblogs.com/lvdabao/p/es6-promise-1.html)
Promise是一个构造函数,自己身上有all、reject、resolve这几个眼熟的方法,原型上有then、catch等同样很眼熟的方法。这么说用Promise new出来的对象肯定就有then、catch方法。
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('执行完成');
resolve('随便什么数据');
}, 2000);
});
Promise的构造函数接收一个参数(是函数),这个参数又传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。其实这里用“成功”和“失败”来描述并不准确,按照标准来讲,resolve是将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected。
在上面的代码中,我们执行了一个异步操作,也就是setTimeout,2秒后,输出“执行完成”,并且调用resolve方法。
运行代码,会在2秒后输出“执行完成”。注意!我只是new了一个对象,并没有调用它,我们传进去的函数就已经执行了,这是需要注意的一个细节。所以我们用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数,如
function runAsync(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('执行完成');
resolve('随便什么数据');
}, 2000);
});
return p;
}
runAsync()
在我们包装好的函数最后,会return出Promise对象,也就是说,执行这个函数我们得到了一个Promise对象。还记得Promise对象上有then、catch方法吧?这就是强大之处了,看下面的代码
runAsync().then(function(data){
console.log(data);
//后面可以用传过来的数据做些其他操作
//......
});
在runAsync()的返回上直接调用then方法,then接收一个参数,是函数,并且会拿到我们在runAsync中调用resolve时传的的参数。运行这段代码,会在2秒后输出“执行完成”,紧接着输出“随便什么数据”。
这时候你应该有所领悟了,原来then里面的函数就跟我们平时的回调函数一个意思,能够在runAsync这个异步任务执行完成之后被执行。这就是Promise的作用了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。
你可能会不屑一顾,那么牛逼轰轰的Promise就这点能耐?我把回调函数封装一下,给runAsync传进去不也一样吗,就像这样:
function runAsync(callback){
setTimeout(function(){
console.log('执行完成');
callback('随便什么数据');
}, 2000);
}
runAsync(function(data){
console.log(data);
});
效果也是一样的,还费劲用Promise干嘛。那么问题来了,有多层回调该怎么办?如果callback也是一个异步操作,而且执行完后也需要有相应的回调函数,该怎么办呢?总不能再定义一个callback2,然后给callback传进去吧。而Promise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。
(链式操作的用法)所以,从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return runAsync3();
})
.then(function(data){
console.log(data);
});
这样能够按顺序,每隔两秒输出每个异步回调中的内容,在runAsync2中传给resolve的数据,能在接下来的then方法中拿到。
在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了,比如我们把上面的代码修改成这样:
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return '直接返回数据'; //这里直接返回数据
})
.then(function(data){
console.log(data);
});
(reject的用法)
到这里,你应该对“Promise是什么玩意”有了最基本的了解。那么我们接着来看看ES6的Promise还有哪些功能。我们光用了resolve,还没用reject呢,它是做什么的呢?事实上,我们前面的例子都是只有“执行成功”的回调,还没有“失败”的情况,reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。看下面的代码。
function getNumber(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
var num = Math.ceil(Math.random()*10); //生成1-10的随机数
if(num<=5){
resolve(num);
}
else{
reject('数字太大了');
}
}, 2000);
});
return p;
}
getNumber()
.then(
function(data){
console.log('resolved');
console.log(data);
},
function(reason, data){
console.log('rejected');
console.log(reason);
}
);
(catch的用法)
我们知道Promise对象除了then方法,还有一个catch方法,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调,用法是这样:
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但
也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。
(all的用法)
Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。我们仍旧使用上面定义好的runAsync1、runAsync2、runAsync3这三个函数,看下面的例子Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操作返回的数据哪里去了呢?都在then里面呢,all会把所有异步操作的结果放进一个数组中传给then,就是上面的results。
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。
(race的用法)
all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync1的延时改为1秒来看一下:
Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
这三个异步操作同样是并行执行的。结果你应该可以猜到,1秒后runAsync1已经执行完了,此时then里面的就执行了,你猜对了吗?不完全,是吧。在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。
这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:
//请求某个图片资源function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = 'xxxxxx';
});
return p;
}
//延时函数,用于给请求计时function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
reject('图片请求超时');
}, 5000);
});
return p;
}
Promise
.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(reason){
console.log(reason);
});
requestImg函数会异步请求一张图片,我把地址写为"xxxxxx",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。运行结果如下:
43.Array.from
ES6为Array增加了from函数用来将其他对象转换成数组。
当然,其他对象也是有要求,也不是所有的,可以将两种对象转换成数组。
1.部署了Iterator接口的对象,比如:Set,Map,Array。
2.类数组对象,什么叫类数组对象,就是一个对象必须有length属性,没有length,转出来的就是空数组。
44.dva/dynamic
解决组件动态加载问题的 util 方法。
比如:
import dynamic from 'dva/dynamic';
const UserPageComponent = dynamic({
app,
models: () => [
import('./models/users'),
],
component: () => import('./routes/UserPage'),
});
opts 包含:
app: dva 实例,加载 models 时需要
models: 返回 Promise 数组的函数,Promise 返回 dva model
component:返回 Promise 的函数,Promise 返回 React Component
45.dva API
* app = dva(opts) --创建应用,返回 dva 实例。(注:dva 支持多实例)
opts 包含:
*history*:指定给路由用的 history,默认是 hashHistory
*initialState*:指定初始数据,优先级高于 model 中的 state,默认是 {}
* app.use({}) --配置 hooks 或者注册插件
* app.model(model) --注册 model
* app.unmodel(namespace) --取消 model 注册,清理 reducers, effects 和 subscriptions。subscription 如果没有返回 unlisten 函数,使用 app.unmodel 会给予警告
* app.router(router) --注册路由表
* app.start('#root') --启动应用
46.react项目使用npm build命令(将开发完的前端代码,利用webpack打包成静态压缩文件)构建出静态文件,这些静态文件可以直接在浏览器打开,之前的财联邦项目发布就是使用的这个方式,同样还可以在服务器安装nodejs,然后把代码clone过去直接用npm start命令也可以启动项目访问页面(在本地开发的时候就是这样)
47.nodejs前端服务器的职责
* 作为静态文件服务器,当用户访问网站的时候,将index.html以及其引入的js、css、fonts以及图片返回给用户
* 负责将客户端发来的ajax请求转发给后台服务器
48.map()中的元素都需要属性key。在哪儿循环就在哪儿设置key
49.key用来作为React的观察点,但它们不会传递给组件。 如果你需要在组件中使用相同的值,则使用不同的名称显式地将它作为props传递:
const content = posts.map(post =>
<Post key={post.id} id={post.id} title={post.title} />
);
使用上面的示例,Post组件可以读取props.id,但不能读取props.key
50.如果有几个组件需要反映相同的变化数据,建议将共享state提升到层级最近的,并且是共同的父组件上。
对于在React应用程序中更改的任何数据,都应该有一个唯一的“数据来源”,也就是state。通常,首先将state添加到需要渲染的组件。如果其他组件也需要它,你可以将其提升到它们层级最近的共同父级组件中。而不是尝试在不同组件之间去同步状态,总归就一句话:你应该依赖于自上而下的数据流
51.devtools
* eval 文档上解释的很明白,每个模块都封装到 eval 包裹起来,并在后面添加 //# sourceURL
* source-map 这是最原始的 source-map 实现方式,其实现是打包代码同时创建一个新的 sourcemap 文件, 并在打包文件的末尾添加 //# sourceURL 注释行告诉 JS 引擎文件在哪儿
* hidden-source-map 文档上也说了,就是 soucremap 但没注释,没注释怎么找文件呢?貌似只能靠后缀,譬如 xxx/bundle.js 文件,某些引擎会尝试去找 xxx/bundle.js.map
* inline-source-map 为每一个文件添加 sourcemap 的 DataUrl,注意这里的文件是打包前的每一个文件而不是最后打包出来的,同时这个 DataUrl 是包含一个文件完整 souremap 信息的 Base64 格式化后的字符串,而不是一个 url。
* eval-source-map 这个就是把 eval 的 sourceURL 换成了完整 souremap 信息的 DataUrl
* cheap-source-map 不包含列信息,不包含 loader 的 sourcemap,(譬如 babel 的 sourcemap)
* cheap-module-source-map 不包含列信息,同时 loader 的 sourcemap 也被简化为只包含对应行的。最终的 sourcemap 只有一份,它是 webpack 对 loader 生成的 sourcemap 进行简化,然后再次生成的。
* 这么多模式,到底该用哪个?
* cheap-module-eval-source-map 绝大多数情况下都会是最好的选择,这也是下版本 webpack 的默认选项。
* 相关解释:
* 大部分情况我们调试并不关心列信息,而且就算 sourcemap 没有列,有些浏览器引擎(例如 v8) 也会给出列信息,所以我们使用 cheap 模式可以大幅提高 souremap 生成的效率。
* 使用 eval 方式可大幅提高持续构建效率,参考 [webapck devtool 文档](https://webpack.github.io/docs/configuration.html#devtool) 下方速度对比表格,这对经常需要边改边调的前端开发而言,非常重要!
* 使用 module 可支持 babel 这种预编译工具(在 webpack 里做为 loader 使用)。
* eval-source-map 使用 DataUrl 本身包含完整 sourcemap 信息,并不需要像 sourceURL 那样,浏览器需要发送一个完整请求去获取 sourcemap 文件,这会略微提高点效率
- 52.React Developer Tools介绍
React Developer Tools 是一款由 facebook 开发的有用的 Chrome浏览器扩展。通过它我们可以查看应用程序的 React 组件分层结构,而不是更加神秘的浏览器 DOM 表示。
注意:该插件只对 ReactJS 开发有效。如果是 React Native 的话则无法使用这个插件调试。
安装完扩展程序后打开扩展程序管理页面。将 React Developer Tools 的“允许访问文件网址”勾选。
-
React Developer Tools使用说明
(1)首先使用 Chrome 打开需要调试的 React 页面,并打开“开发者工具”。
(2)在“开发者工具”上方工具栏最右侧会有个 react 标签。点击这个标签就可以看到当前应用的结构。
通过 React Developer Tools 我们可以很方便地看到各个组件之间的嵌套关系以及每个组件的事件、属性、状态等信息。
(3)React Developer Tools会自动检测React组件,不过在webpack-dev-server模式下,webpack会自动将React组件放入到iframe下,导致React组件检测失败,变通方法是webpack-dev-server配置在--inline模式下即可:
webpack-dev-server --inline
(5)修改某一处为错误,然后观察结果
56. this.props.children
this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点(查看 demo05)。
var NotesList = React.createClass({
render: function() {
return (
<ol>
{
React.Children.map(this.props.children, function (child) {
return <li>{child}</li>;
})
}
</ol>
);
}});
ReactDOM.render(
<NotesList>
<span>hello</span>
<span>world</span>
</NotesList>,
document.body
);
上面代码的 NoteList 组件有两个 span 子节点,它们都可以通过 this.props.children 读取。
这里需要注意, this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined ;如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。所以,处理 this.props.children 的时候要小心。
React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。更多的 React.Children 的方法,请参考官方文档。
props.children可以像任何其他props那样工作,它可以传递任何类型的数据,并不局限于那些告诉React应该如何渲染的东东。 例如,如果您有一个自定义组件,您可以将props.children作为一个回调函数:
-
布尔值、null、undefined在渲染时会被自动忽略
false,null,undefined和true是有效的子元素,不过他们从根本上讲是不参与渲染的。 这些JSX表达式将渲染处相同的东西:
如果你想要一个值如false,true,null或undefined出现在输出中,你必须先将它转换为字符串:
57.JSX会删除行的开始和结尾处的空格。 它也会删除中间的空行。 与标签相邻的空行被会被删除;
在字符串文本中间出现的空行会缩合成一个空格。
58.instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
object instanceof constructor (object--要检测的对象,constructor--某个构造函数)
- 59.window.scrollTo和window.scrollBy (to是绝对的意思(从整体而言),by是相对的意思(从原先的位置而言)
scrollTo:在窗体中如果有滚动条,将横向滚动条移动到相对于窗体宽度为x个
像素的位置,将纵向滚动条移动到相对于窗体高度为y个像素的位置
scrollBy:(要使此方法工作 window 滚动条的可见属性必须设置为true!)
如果有滚动条,将横向滚动条移动到相对于当前横向滚动条的x个像素的位置
(就是向左移动x像素),将纵向滚动条移动到相对于当前纵向滚动条高度为y个像素的位置(就是向下移动y像素)
60.npm --save-dev --save 的区别
npm install 在安装 npm 包时,有两种命令参数可以把它们的信息写入 package.json 文件,一个是npm install--save另一个是 npm install –save-dev,他们表面上的区别是--save 会把依赖包名称添加到 package.json 文件 dependencies 键下,--save-dev 则添加到 package.json 文件 devDependencies 键下,不过这只是它们的表面区别。它们真正的区别是,npm自己的文档说dependencies是运行时依赖,devDependencies是开发时的依赖。即devDependencies 下列出的模块,是我们开发时用的,比如 我们安装 js的压缩包gulp-uglify 时,我们采用的是 “npm install –save-dev gulp-uglify ”命令安装,因为我们在发布后用不到它,而只是在我们开发才用到它。dependencies 下的模块,则是我们发布后还需要依赖的模块,譬如像jQuery库或者Angular框架类似的,我们在开发完后后肯定还要依赖它们,否则就运行不了。
另外需要补充的是:
正常使用npm install时,会下载dependencies和devDependencies中的模块,当使用npm install –production或者注明NODE_ENV变量值为production时,只会下载dependencies中的模块
61.利用window.onscroll事件实现滚动条滚动分页
//滚动滑轮触发scrollFunc方法 //ie 谷歌
window.onscroll = () => {
this.loadMore();
};
//加载更多
loadMore = () => {
const {form: {getFieldValue}, chechData} = this.props;
let tempTotal = 0,page;
for (let index in chechData.groups) {
tempTotal += chechData.groups[index].homework.length;
}
page = Math.floor(tempTotal/10) + 1;
this.isVisible = this.isMoreVisible("#loadMore");
if (this.isVisible) {
let homewordParas: any = {
page: page,
pageSize: this.state.pageSize,
subjectId: this.props.subjectId,
sessionId: getFieldValue('classId')?getFieldValue('classId'):null,
timeRangeCode: getFieldValue('fromDate')?(getFieldValue('fromDate') === '近两周'?'TWO_WEEKS':getFieldValue('fromDate')):null,
assessmentTitle:getFieldValue('homeworkName')?getFieldValue('homeworkName'):null,
};
if (tempTotal < this.props.chechData.total ) {
this.setState({
page: homewordParas.page
});
this.props.getHttpCheckData(homewordParas);
} else {
this.props.chechData? (this.refs.loadMore as any).innerHTML = "已没有更多作业!":(this.refs.loadMore as any).innerHTML = "";
}
window.onscroll = null;
}
};
62.HTML5 的 Audio 标签
到今天为止,大多数的音频文件播放,是通过 Flash 来实现的。而 HTML5 定义了一个新元素「audio」,在播放音频上为我们提供了很多方便的功能。
在浏览器的支持上,<audio> 标签目前只支持 Internet Explorer 9+, Firefox, Opera, Chrome 和 Safari。也就是说,IE 9 以下是不支持的。
标签属性:
* src: 要播放的音频的 URL。
* preload: 是否预加载,如果使用 "autoplay",则忽略该属性。
* autoplay: 是否自动播放。
* loop: 是否循环播放。
* controls: 是否显示浏览器自带的控制条,例如播放按钮。
html的写法:
audio对象的方法和属性:
我们可以动态把一个audio元素插入到页面中,从而通过 JS 来获取这个对象,简单的方法如下:
事件:
63. addEventListener 用法 (https://www.cnblogs.com/Andyudd/p/5583563.html)
addEventListener 用于注册事件处理程序,IE 中为 attachEvent,我们为什么讲 addEventListener 而不讲 attachEvent 呢?一来 attachEvent 比较简单,二来 addEventListener 才是 DOM 中的标准内容。
addEventListener 为文档节点、document、window 或 XMLHttpRequest 注册事件处理程序,在以前我们一般是 <input type="button" onclick="...",或 document.getElementById("testButton").onclick = FuncName, 而在 DOM 中,我们用 addEventListener(IE 中用 attachEvent)。
语法:
target.addEventListener(type, listener, useCapture);
- target 文档节点、document、window 或 XMLHttpRequest。
- type 字符串,事件名称,不含“on”,比如“click”、“mouseover”、“keydown”等。
- listener 实现了 EventListener 接口或者是 JavaScript 中的函数。
- useCapture 是否使用捕捉,看了后面的事件流一节后就明白了,一般用 false。
64.addEventListener-事件流
当一个事件发生时,分为三个阶段:
捕获阶段-- 从根节点开始顺序而下,检测每个节点是否注册了事件处理程序。如果注册了事件处理程序,并且 useCapture 为 true,则调用该事件处理程序。(IE 中无此阶段。)
目标阶段-- 触发在目标对象本身注册的事件处理程序,也称正常事件派发阶段。
冒泡阶段-- 从目标节点到根节点,检测每个节点是否注册了事件处理程序,如果注册了事件处理程序,并且 useCapture 为 false,则调用该事件处理程序。
举例
<div id="div1">
<div id="div2">
<div id="div3">
<div id="div4">
</div>
</div>
</div>
</div>
如果在 d3 上点击鼠标,事件流是这样的:
捕获阶段 在 div1 处检测是否有 useCapture 为 true 的事件处理程序,若有,则执行该程序,然后再同样地处理 div2。
目标阶段 在 div3 处,发现 div3 就是鼠标点击的节点,所以这里为目标阶段,若有事件处理程序,则执行该程序,这里不论 useCapture 为 true 还是 false。
冒泡阶段 在 div2 处检测是否有 useCapture 为 false 的事件处理程序,若有,则执行该程序,然后再同样地处理 div1。
注意,上述捕获阶段和冒泡阶段中,实际上 div1 之上还应该有结点,比如有 body,但这里不讨论。
65. addEventListener-第三个参数 useCapture
addEventListener 用于注册事件处理程序,IE 中为 attachEvent,我们为什么讲 addEventListener 而不讲 attachEvent 呢?一来 attachEvent 比较简单,二来 addEventListener 才是 DOM 中的标准内容。
- 简介
addEventListener 为文档节点、document、window 或 XMLHttpRequest 注册事件处理程序,在以前我们一般是 <input type="button" onclick="...",或 document.getElementById("testButton").onclick = FuncName, 而在 DOM 中,我们用 addEventListener(IE 中用 attachEvent)。
2.语法
target.addEventListener(type, listener, useCapture);
- target 文档节点、document、window 或 XMLHttpRequest。
- type 字符串,事件名称,不含“on”,比如“click”、“mouseover”、“keydown”等。
- listener 实现了 EventListener 接口或者是 JavaScript 中的函数。
- useCapture 是否使用捕捉,看了后面的事件流一节后就明白了,一般用 false。
3.示例
function Go()
{
//...
}
document.getElementById("testButton").addEventListener("click", Go, false);
或者 listener 直接就是函数
document.getElementById("testButton").addEventListener("click", function () { ... }, false);
addEventListener-事件流
说到 addEventListener 不得不说到事件流,先说事件流对后面的解释比较方便。
当一个事件发生时,分为三个阶段:
捕获阶段 从根节点开始顺序而下,检测每个节点是否注册了事件处理程序。如果注册了事件处理程序,并且 useCapture 为 true,则调用该事件处理程序。(IE 中无此阶段。)
目标阶段 触发在目标对象本身注册的事件处理程序,也称正常事件派发阶段。
冒泡阶段 从目标节点到根节点,检测每个节点是否注册了事件处理程序,如果注册了事件处理程序,并且 useCapture 为 false,则调用该事件处理程序。
举例
<div id="div1">
<div id="div2">
<div id="div3">
<div id="div4">
</div>
</div>
</div>
</div>
如果在 d3 上点击鼠标,事件流是这样的:
捕获阶段 在 div1 处检测是否有 useCapture 为 true 的事件处理程序,若有,则执行该程序,然后再同样地处理 div2。
目标阶段 在 div3 处,发现 div3 就是鼠标点击的节点,所以这里为目标阶段,若有事件处理程序,则执行该程序,这里不论 useCapture 为 true 还是 false。
冒泡阶段 在 div2 处检测是否有 useCapture 为 false 的事件处理程序,若有,则执行该程序,然后再同样地处理 div1。
注意,上述捕获阶段和冒泡阶段中,实际上 div1 之上还应该有结点,比如有 body,但这里不讨论。
addEventListener-第三个参数 useCapture
addEventListener 有三个参数:第一个参数表示事件名称(不含 on,如 "click");第二个参数表示要接收事件处理的函数;第三个参数为 useCapture,本文就讲解它。
<div id="outDiv">
<div id="middleDiv">
<div id="inDiv">请在此点击鼠标。</div>
</div>
</div>
<div id="info"></div>
var outDiv = document.getElementById("outDiv");
var middleDiv = document.getElementById("middleDiv");
var inDiv = document.getElementById("inDiv");
var info = document.getElementById("info");
outDiv.addEventListener("click", function () { info.innerHTML += "outDiv" + "<br>"; }, false);
middleDiv.addEventListener("click", function () { info.innerHTML += "middleDiv" + "<br>"; }, false);
inDiv.addEventListener("click", function () { info.innerHTML += "inDiv" + "<br>"; }, false);
上述是我们测试的代码,根据 info 的显示来确定触发的顺序,有三个 addEventListener,而 useCapture 可选值为 true 和 false,所以 222,可以得出 8 段不同的程序。
全为 false 时,触发顺序为:inDiv、middleDiv、outDiv;
全为 true 时,触发顺序为:outDiv、middleDiv、inDiv;
outDiv 为 true,其他为 false 时,触发顺序为:outDiv、inDiv、middleDiv;
middleDiv 为 true,其他为 false 时,触发顺序为:middleDiv、inDiv、outDiv;
……
最终得出如下结论:true 的触发顺序总是在 false 之前;
如果多个均为 true,则外层的触发先于内层;
如果多个均为 false,则内层的触发先于外层。
66.react中样式通过className引入
①className={style["simple-audio-choice-wrap"]}
②className={style.ors_row_pc_list}
第一种方法的格式是因为样式名称使用了“-”,如果用第二种引入会报错
67.className引入多个样式
className={${style.a} ${style.b}
}
68.pop() 方法将删除 arrayObject 的最后一个元素,把数组长度减 1,并且返回它删除的元素的值。如果数组已经为空,则 pop() 不改变数组,并返回 undefined 值。
69.在package.json文件的webpack-dev-server.js 加--host 0.0.0.0,启动项目,可以使用手机输入电脑的ip对本地项目进行访问。这个配置的含义是,既可以再本地用http://localhost/yoursite的url访问项目,也可以用http://yourip/yoursite访问项目。
比如你的计算机IP地址是192.168.1.10,项目名称是app,那么你的访问url就可以是http://localhost/app,或者http://192.168.1.10/app。
这样设置的好处是,当你想用非本机访问项目进行测试时,由ip地址构建的url可以对项目进行访问,同时本地还可以用localhost进行访问。如果你将ip写死了,那么localhost就无法访问了;
70.reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
reduce() 可以作为一个高阶函数,用于函数的 compose。
注意: reduce() 对于空数组是不会执行回调函数的。
71.new Date().toLocaleString() toLocaleString() 方法可根据本地时间把 Date 对象转换为字符串,并返回结果。2018/3/19 下午8:45:14
72.cordova plugin add cordova-plugin-statusbar
Android 5.0以下 支持的接口比较少show()和hide() 等
1. StatusBar.isVisible 是否已经显示
2. StatusBar.hide(); 隐藏状态栏
3. StatusBar.show(); 显示状态栏
Android 5.0以上,支持设置背景
使用十六进制设置背景:
StatusBar.backgroundColorByHexString("#C0C0C0");
StatusBar.backgroundColorByHexString("#333"); // => #333333
StatusBar.backgroundColorByHexString("#FAB"); // => #FFAABB
使用名称设置背景:
StatusBar.backgroundColorByName("red");
document.addEventListener("deviceready", () => {
// set statusbar background color
if (cordova.platformId == 'android') {
StatusBar.backgroundColorByHexString("#f9f9f9");
}
}
73.typescript中使用Form报错但不影响运行
先这样写吧,这个类型暂时有点搞不定。
Form.create<any>()(LoginForm)
74.改变当前组件的state值用setState,想改变props里面的值怎么做:
①当前组件如果是 connect 到 redux 就通过 dispatch 改。
第一: 就是 dispatch 一个 action 在 reduce 里面修改 store 的 state
第二:再组件加载完数据之后,把 props 的数据 放到组件的 state 里面,组件怎么变,就用setState 去让组件重新渲染。可以在
componentWillReceiveProps函数中把props 的数据 放到组件的 state中
②当前组件如果是 某个父组件 传递的 props 组件 的子组件, 子组件内部可以调用 this.props.onChange 通过事件的方式 把新的 props 传给父组件
然后父亲组件去改。Input 显示的值value就是 通过 props 传进去的,然后 Input 的值更新。需要改变 props的 value是直接通过 onChange 通知父组件 改变
75.Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性,但是Object.assign 不会跳过那些值为 null 或 undefined 的源对象
76.hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
77.在saga文件里面获取store上的数据:
①可以通过select方法获取,如const cartData = yield select((state: any) => state.homeworkCartReducer.cartData);
②(window as GlobalDefinitions).store.getState().homeworkCenterReducer.subjectId
78.map只能遍历数组,其他用for...in...,或者排除掉那个非枚举属性再map
79..遇到过两次setState设置某个属性的值,在回调函数中打印发现没设置成功,都是因为同一个函数里面写了多个setState,一定要注意,后面的setState可能会把前面set的值给覆盖掉
80.可利用Jquery的 .text方法实时计算文本域还能输入多少字符:总数-已输入
81.html文件是自上而下的执行方式,但引入的css和javascript的顺序有所不同,css引入执行加载时,程序仍然往下执行,而执行到<script>脚本是则中断线程,待该script脚本执行结束之后程序才继续往下执行。所以,大部分网上讨论是将script脚本放在<body>之后,那样dom的生成就不会因为长时间执行script脚本而延迟阻塞,加快了页面的加载速度。但又不能将所有的script放在body之后,因为有一些页面的效果的实现,是需要预先动态的加载一些js脚本。所以这些脚本应该放在<body>之前。其次,不能将需要访问dom元素的js放在body之前,因为此时还没有开始生成dom,所以在body之前的访问dom元素的js会出错,或者无效。所以,我认为script放置位置的原则“页面效果实现类的js应该放在body之前,动作,交互,事件驱动,需要访问dom属性的js都可以放在body之后”。
82.<script> 标签的 defer 属性
defer 属性规定是否对脚本执行进行延迟,直到页面加载为止。
有的 javascript 脚本 document.write 方法来创建当前的文档内容,其他脚本就不一定是了。
如果您的脚本不会改变文档的内容,可将 defer 属性加入到 <script> 标签中,以便加快处理文档的速度。因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止。
83.css
怎么找到下面的ant-menu-item-selecte这个class?
①.main_left_content :global(.ant-menu-item-selected)
②.main_left_content>:global(.ant-menu-item-selected)
注意:>隔开表示 子集(向下一层);空格 隔开表示 子集或子孙集 (向下多层,范围更广)
84.&
.el-row {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
注意:& 代表上一级选择器,实际编译成css就是 .el-row:last-child{}
85.覆盖antd样式不生效时,试着在需要覆盖的样式前加 :gloabal
antd 的类都要加上 global ,要不然会被 webpack css modules 转换 class 的字符串
86.css选择器
87.7制造间距的方法
①将一个div设成间距,间距的颜色为div的背景色
②给其中一个div设置border,border的宽度即为间距的宽度
③利用box-shadow,将第一个和第三个参数都设置为0,第二个参数为间距的宽度,注意:同时要给margin值,投影到top方向就要给margin-top
88.onsenui实现下拉列表展开样式仿淘宝
89.充分利用before和after属性
90.js的delete属性
delete 操作符用于删除对象的某个属性;如果没有指向这个属性的引用,那它最终会被释放
91. javascript 6个falsy值:null,undefined,NaN,0,false,空字符串
92.从数组中移除某些值(lodash)http://www.bubuko.com/infodetail-794683.html
①.difference(array,[values]) 两个参数都是数组,array:要被检查/过滤的数组, array:要被检查/过滤的数组
② .drop(array,number) 类似于原生js方法中的slice。从头开始删除number个数组元素。number不传的话默认按1处理
③.without(array, [values]) 不同于difference方法。其values参数可以不是一个数组,而是接在array参数之后的零散参数。
④.remove(array, [predicate=_.identity], [thisArg]) (对原数组操作) 第二个参数为function
⑤ .pull(array, [values]) (对原数组操作)
⑥.rest(array) 移除数组首元素 和initial相反
85.js原生的indexOf和lodash中的indexOf
js:
①语法: stringObject.indexOf(searchvalue,fromindex)
②注意: 对大小写敏感! 如果要检索的字符串值没有出现,则该方法返回 -1。
③例子:
lodash:
①语法: _.indexOf(array, value, [fromIndex=0])
②注意:第一个参数必须是数组
③例子:
93.一个大div包含两个div,怎么让左边的固定宽度,另一个撑满剩下宽度
(1)使用float
<div class="use-float">
<div></div>
<div></div></div>
.use-float>div:first-child{
width:100px;
float:left;}.use-float>div:last-child{
overflow:hidden;}
(2)使用table
<table class="use-table">
<tr>
<td></td>
<td></td>
</tr>
</table>
.use-table{
border-collapse:collapse;
width:100%;}.use-table>tbody>tr>td:first-child{
width:100px;}
(3)用div模拟table
<div class="use-mock-table">
<div></div>
<div></div></div>
.use-mock-table{
display:table;
width:100%;}.use-mock-table>div{
display:table-cell;}.use-mock-table>div:first-child{
width:100px;}
(4)使用flex
<div class="use-flex">
<div></div>
<div></div></div>
.use-flex{
display:flex;}.use-flex>div:first-child{
flex:none;
width:100px;}.use-flex>div:last-child{
flex:1;}
94.数组去重
① Set是ES6中新的对象。 利用它可以迅速为数组去重
简单来说,Set于Array的区别在于:Array中允许出现重复的元素,例如[1,2,2,3];而Set中的所有元素都是唯一的,只能是{1,2,3}。利用这一特性,我们就可以迅速地去掉数组中重复的元素。
let sessionListAll = Array.from(new Set(sessionData));
classchollArrayId= [...new Set(classchollArrayId)]//数组去重
②lodash uniq/unique 数组去重
③lodash union数组合并去重
95.javascript中for of和for in的区别
①推荐在循环对象属性的时候,使用for...in,在遍历数组的时候的时候使用for...of。
②for...in循环出的是key,for...of循环出的是value
③注意,for...of是ES6新引入的特性。修复了ES5引入的for...in的不足
④for...of不能循环普通的对象,需要通过和Object.keys()搭配使用
95. npm 装包时的区别 -D -S
https://www.limitcode.com/detail/59a15b1a69e95702e0780249.html
回顾 npm install 命令最近在写Node程序的时候,突然对 npm install 的-save和-save-dev 这两个参数的使用比较混乱。其实博主在这之前对这两个参数的理解也是模糊的,各种查资料和实践后对它们之间的异同点略有理解。遂写下这篇文章避免自己忘记,同时也给node猿友一点指引。
我们在使用 npm install 安装模块的模块的时候 ,一般会使用下面这几种命令形式:
npm install moduleName # 安装模块到项目目录下
npm install -g moduleName # -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。
npm install -save moduleName # -save 的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。
npm install -save-dev moduleName # -save-dev 的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。
那么问题来了,在项目中我们应该使用四个命令中的哪个呢?这个就要视情况而定了。下面对这四个命令进行对比,看完后你就不再这么问了。
- npm install moduleName 命令
- 安装模块到项目node_modules目录下。
- 不会将模块依赖写入devDependencies或dependencies 节点。
- 运行 npm install 初始化项目时不会下载模块。
- npm install -g moduleName 命令
- 安装模块到全局,不会在项目node_modules目录中保存模块包。
- 不会将模块依赖写入devDependencies或dependencies 节点。
- 运行 npm install 初始化项目时不会下载模块。
- npm install -save * * moduleName 命令
- 安装模块到项目node_modules目录下。
- 会将模块依赖写入dependencies 节点。
- 运行 npm install 初始化项目时,会将模块下载到项目目录下。
- 运行npm install --production或者注明NODE_ENV变量值为production时,会自动下载模块到node_modules目录中。
- npm install -save-dev moduleName 命令
- 安装模块到项目node_modules目录下。
- 会将模块依赖写入devDependencies 节点。
- 运行 npm install 初始化项目时,会将模块下载到项目目录下。
- 运行npm install --production或者注明NODE_ENV变量值为 production时,不会自动下载模块到node_modules目录中。总结
devDependencies 节点下的模块是我们在开发时需要用的,比如项目中使用的 gulp ,压缩css、js的模块。这些模块在我们的项目部署后是不需要的,所以我们可以使用 -save-dev 的形式安装。像 express 这些模块是项目运行必备的,应该安装在 dependencies 节点下,所以我们应该使用 -save 的形式安装。
96.XML DOM - XMLHttpRequest 对象
97.名词理解
同构应用:什么是前后端同构呢?就是前后端都可以使用同一套代码生成页面,页面既可以由前端动态生成,也可以由后端服务器直接渲染出来
单页面应用:就是只有一张Web页面的应用。单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。 浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。直白一点就是只有一个html文件的应用。react,vue等做的都是单页面的
98.Redux
① 要想更新 state 中的数据,你需要发起一个 action
②把 action 和 state 串起来,开发一些函数,这就是 reducer。reducer 只是一个接收 state 和 action,并返回新的 state 的函数
- Redux三大原则
①单一数据源: 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。 可通过store.getState()取store上的数据
②State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
③使用纯函数来执行修改:为了描述 action 如何改变 state tree ,你需要编写 reducers。
99.在saga中使用setTimeout
const delay =(timeout: any) => new Promise((callback) => setTimeout(callback,timeout))
yield delay(2000);
100. redux-saga
https://redux-saga-in-chinese.js.org/docs/api/index.html
put 就是我们所说的一个调用 Effect 的例子
call 就像 put, 返回一个指示 middleware 以给定参数调用给定的函数的 Effect。
takeEvery 允许多个 fetchData 实例同时启动。在某个特定时刻,我们可以启动一个新的 fetchData 任务, 尽管之前还有一个或多个 fetchData 尚未结束。
takeLatest 得到最新那个请求的响应(例如,始终显示最新版本的数据)。 和 takeEvery 不同,在任何时刻 takeLatest 只允许执行一个 fetchData 任务。并且这个任务是最后被启动的那个。 如果之前已经有一个任务在执行,那之前的这个任务会自动被取消。
在 Generator 函数中,yield 右边的任何表达式都会被求值,结果会被 yield 给调用者
fork 无阻塞调用。 当我们 fork 一个 任务,任务会在后台启动,调用者也可以继续它自己的流程,而不用等待被 fork 的任务结束。 yield fork 的返回结果是一个 Task 对象
cancel 为了取消 fork 任务,使用的一个指定的 Effect 。
race
yield* 操作符来组合多个 Sagas,使得它们保持顺序。 这让你可以一种简单的程序风格来排列你的 宏观任务(macro-tasks)。
101. request 请求头
credentials: 'include' : 默认情况下,标准的跨域请求是不会发送cookie等用户认证凭据的。所以,当你访问远程api的时候,cookie是不会被带上的。【 当这个属性为true的时候,远程服务器也要作相应的处理。在响应头那里设置 Access-Control-Allow-Credentials: true 】
102.previousSibling属性返回元素节点之前的兄弟节点(包括文本节点、注释节点);
previousElementSibling属性只返回元素节点之前的兄弟元素节点(不包括文本节点、注释节点);
103.前端文件下载
①<a />标签:<a href={"/api/fms/sys/attach/file/detail?"+stringify(params)} style={{marginLeft:'200px'}}>下载</a>
txt,png,jpg等这些浏览器支持直接打开的文件是不会执行下载任务的,而是会直接打开文件,这个时候就需要给a标签添加一个属性“download”;
②window.location.href = "..."
104.图片预览
使用<img />标签,在src属性中写上图片存放的地址:<img src={PICTURE_ADDRESS + srcPath} alt="财联邦" style={{width: '600px',height:'400px'}}/>
105.①_.uniqWith(array, [comparator])
②.xorBy([arrays], [iteratee=.identity])
3)_.xorWith([arrays], [comparator])
-
106.对象中数组对象去重
1)import _ from "lodash";
for (let index in checkData.groups) {
let homework = _.uniqWith(checkData.groups[index].homework, _.isEqual);
checkData.groups[index].homework = homework;
}
2)
/**
*
* @param {*} list 数组
* @param {*} fields 去重字段
*/
function uniqeItem(list:any, fields:any){
function getUniquKey(item:any, fields:any){
let length = fields.length, key = [];
while(length--){
key.push(item[fields[length]]);
}
return key.join('|');
}
let newList = [];
let keyMap = new Map();
for (let i = 0, j = list.length; i < j; i++) {
let key = getUniquKey(list[i], fields);
if (!keyMap.get(key)) {
keyMap.set(key, true);
newList.push(list[i]);
}
}
return newList ;
}
uniqeItem([{a:1,b:2},{a:1,b:2}], ['a','b']);
107.8到20位的可输入半角符号,但不含空格并且至少包含一个数字和字母
/^(?=.*[0-9])(?=.*[a-zA-Z])[\x01-\x1f|\x21-\xff]{8,20}$/
108.控制台报错解决
-
1.message全局提示报错,之前以为是DatePicker组件的校验问题
定位问题到message组件:①通过下面的created by notification;createed by animate;②通过保存函数,应该弹出提示信息,但是没有弹出
-
2.Redirect to属性报错
定位问题:通过 '>' expected可以想到>是html片段符号,而文件后缀是ts,也就是文件类型应该是tsx。所以把文件后缀改为tsx,问题解决
-
3.DatePicker组件总是显示成英文
问题解决:
109.antd中父组件怎么使用子组件的form中自带的属性
-
1.父组件将form传给子组件,子组件使用父组件传下来的form
-
2.使用官网API
①在父组件ExamCreation中不使用Form
②在各个子组件中使用Form,然后在父组件ExamCreation中调用的子组件上加上那个属性,最后就可以像在子组件一样使用form中的API了。(注意:使用typeScript时一定要先定义this.form3...)
110. antd升级到3.6.5版本后遇到问题及解决
-
1.Pagination组件显示成英文
解决方法:
①引入antd国际化组件LocaleProvider 和需要的语言包
②用国际化组件将分页组件包裹起来
-
2.DatePicker组件显示成英文
解决方法:除了上面使用LocaleProvider 组件解决,DatePicker组件有自带的语言包
111.在git上提merge request的时候,上面检测到的更改是,自己的远程分支和master分支代码对比的结果
112.使用Redirect的时候报错
Redirect是一个组件,将组件嵌套在ts代码中,文件类型要改为tsx
113.浏览器在传递url的时候,会使用自己的编码格式对地址进行编码,如果浏览器所使用编码与服务器采用编码不一致,服务器接收到的参数就会出现乱码。在firefox,chrome下正常,ie下会出现乱码。
解决方法:使用js encodeURI 对地址进行统一编码,encodeURI("article/detail?title=我是中文");
114.阻止冒泡:e.stopPropagation(),IE使用e.cancelBubble = true
if (e && e.stopPropagation) {
e.stopPropagation();
} else if (window.event) {
window.event.cancelBubble = true;
}
115.日期格式转时间戳
1.Date.parse(date) (注意浏览器之间的区别、兼容)
IE:
Firefox:
Chrome:
- new Date(date).getTime()
IE:
Firefox:
Chrome:
注意:两种方法在三个浏览器都能起作用的日期格式是 / 分割的,所以要使用该方法的话使用 .replace()转化,如IE下的:
116.Moment.js常用API
https://blog.csdn.net/wulex/article/details/80402036
117.修改滚动条默认样式
::-webkit-scrollbar {
width: 6px;
//height: 16px;
background-color: #F5F5F5;
}
/*定义滚动条轨道 内阴影+圆角*/
::-webkit-scrollbar-track {
//-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
opacity: 0.5;
border-radius: 10px;
background-color: #F5F5F5;
}
/*定义滑块 内阴影+圆角*/
::-webkit-scrollbar-thumb {
border-radius: 10px;
//-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
background-color: #dddddd;
}
118.Dva中container中的js文件
第1,2个customer是在model中的命名空间,第3,4个是同一个
119.Classnames
根据状态值动态添加或去除class.
npm install classnames
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
可以看到键值为true的就返回键名,可以利用这个方法来动态控制键值的true/fale变化,从而控制是否返回键。(默认是返回的)
// ES5
let buttonType = 'primary';
classNames({ [`btn-${buttonType}`]: true });
在React中使用
var Button = React.createClass({
// ...
render () {
var btnClass = 'btn';
//根据点击的state来控制css
if (this.state.isPressed) btnClass += ' btn-pressed';
else if (this.state.isHovered) btnClass += ' btn-over';
return <button className={btnClass}>{this.props.label}</button>;
}
});
var classNames = require('classnames');
var Button = React.createClass({
// ...
render () {
var btnClass = classNames({
'btn': true,
'btn-pressed': this.state.isPressed,
'btn-over': !this.state.isPressed && this.state.isHovered
});
return <button className={btnClass}>{this.props.label}</button>;
}
});
如果是name和className进行了映射,可以使用bind方法
/* components/submit-button.js */
import { Component } from 'react';
import classNames from 'classnames/bind';
import styles from './submit-button.css';
let cx = classNames.bind(styles);
export default class SubmitButton extends Component {
render () {
let text = this.props.store.submissionInProgress ? 'Processing...' : 'Submit';//text根据状态来动态加载
let className = cx({
base: true,
inProgress: this.props.store.submissionInProgress,//样式的动态加载
error: this.props.store.errorOccurred,
disabled: this.props.form.valid,
});
return <button className={className}>{text}</button>;
}
};
120.path-to-regexp
使用path-to-regexp,我们可以在路径字符串中使用正则。如/:foo*/:bar?、/icon-:foo(\d+).png等。
直接调用构造函数使用,一个可能含某种匹配模式的路径字符串作为它的必选参数,它返回一个正则对象。
const pathToRegexp = require('path-to-regexp')
var regexp_1 = pathToRegexp('/foo/:bar')// /^\/foo\/([^\/]+?)(?:\/)?$/i
regexp_1.exec('/foo/barrrr')//匹配成功 =>RegExpExecArray [ '/foo/barrrr', 'barrrr', index: 0, input: '/foo/barrrr' ]
regexp_1.exec('/bazzzz')//匹配失败 => null
/foo/:bar中的/为分隔符,把多个匹配模式分隔开,这里就分成foo和:bar。像foo这种不带:前缀的,我们请求的路径需要和它完全匹配,而:bar这种,叫命名参数,就像个函数形参,可以传递任何请求路径字串给它。
在命名参数上,我们可以使用参数修饰符作为其后缀,有?、+、*
- “*”:表示我这个命名参数:bar可以接收随意个匹配模式,就好像参数数组长度[0,+∞)
var regexp_2 = pathToRegexp('/foo/:bar*')
regexp_2.exec('/foo/a/b/c')// => [ '/foo/a/b/c', 'a/b/c', index: 0, input: '/foo/a/b/c' ]
regexp_2.exec('/foo')// => [ '/foo', undefined, index: 0, input: '/foo' ]
- “+”: 表示命名参数可以接收至少一个匹配模式,一个都没就匹配失败,[1,+∞)
var regexp_3 = pathToRegexp('/foo/:bar+')
regexp_3.exec('/foo/a/b/c')// => [ '/foo/a/b/c', 'a/b/c', index: 0, input: '/foo/a/b/c' ]
regexp_3.exec('/foo')//匹配失败 => null
- “? ”:表示命名参数可以接收0个或1个匹配模式,多个失败,[0,1]
var regexp_4 = pathToRegexp('/foo/:bar?')
regexp_4.exec('/foo/a')// => [ '/foo/a', 'a', index: 0, input: '/foo/a' ]
regexp_4.exec('/foo/a/b/c')// => null
regexp_4.exec('/foo')// => [ '/foo', undefined, index: 0, input: '/foo' ]
我们还可以为命名参数加上自定义的正则匹配模式
// 以下设置表示:foo只能是数字。
var regexp_5 = pathToRegexp('/icon-:foo(\\d+).png')
regexp_5.exec('/icon-123.png')// => [ '/icon-123.png', '123', index: 0, input: '/icon-123.png' ]
regexp_5.exec('/icon-abc.png')// null
//不需要命名参数这个占位符,通过正则就能就能匹配。
var regexp_5 = pathToRegexp('/icon-(\\d+).png')
regexp_5.exec('/icon-123.png')// => [ '/icon-123.png', '123', index: 0, input: '/icon-123.png' ]
regexp_5.exec('/icon-abc.png')// null
121. Form 提交需注意
把提交函数写在Form上会导致:表单提交后在地址栏会添一个“?”,界面刷新
122.转换location.search为json格式
// js
export function getSearchJson(search) {
if(search == undefined) return
search = search.substr(1);
let arr = search.split("&"),
obj = {},
newArr = [];
arr.map((value)=>{
newArr = value.split("=");
if(newArr[0] !== undefined) {
obj[newArr[0]] = newArr[1];
}
})
return obj;
};
// 使用插件
npm i query-string
import queryString from 'query-string';
const { dispatch, location: { search } } = this.props;
const queryParams = queryString.parse(search) || {};
123.字段描述值根据值集展示
render: (text, record)=>{
if(text && this.state.code.checkStatus){
for(let i in this.state.code.checkStatus){
if(text == this.state.code.checkStatus[i].value){
return(<div>{this.state.code.checkStatus[i].meaning}</div>)
}
}
}
}
124.no-param-reassign
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。“`
@param: obj:需要定义属性的对象;
prop:需要定义或修改的属性;
descriptor:将被定义或修改属性的描述符
对象里目前存在的属性描述符主要有两种形式: 数据描述符和存取描述符.
125.<img />标签图片文件不更新问题
不更新的原因:更新src后,如果src与原来的相同,则浏览器回从缓存里获取图片而不会向后台发送新的请求
解决办法:在src之后加上一些没有意义的随机参数比如链接上“?time=new Date().getTime()”即获取当前时间的时间戳,这是浏览器会认为这是不同的url因此会重新发送请求加载新的图片。 改成如下样子会导致新的问题:每次进入界面都请求好几次
多次请求原因:props中有些变量值改变导致重新render,如loading由false变成true又变成false
解决方法:将url后的随机变量的值从models中传过来
126.Context API
- React.createContext 方法用于创建一个 Context 对象。该对象包含 Provider 和 Consumer两个属性,分别为两个 React 组件。
- Provider 组件。用在组件树中更外层的位置。它接受一个名为 value 的 prop,其值可以是任何 JavaScript 中的数据类型。
- Consumer 组件。可以在 Provider 组件内部的任何一层使用。它接收一个名为 children 值为一个函数的 prop。这个函数的参数是 Provider 组件接收的那个 value prop 的值,返回值是一个 React 元素(一段 JSX 代码)。
特点:
-
Provider
和Consumer
必须来自同一次React.createContext
调用。也就是说NameContext.Provider
和AgeContext.Consumer
是无法搭配使用的。 -
React.createContext
方法接收一个默认值作为参数。当Consumer
外层没有对应的Provider
时就会使用该默认值。 -
Provider
组件的value
prop 值发生变更时,其内部组件树中对应的Consumer
组件会接收到新值并重新执行children
函数。此过程不受shouldComponentUpdete
方法的影响。前面的示例代码中,Hello
组件继承自React.PureComponent
但页面依然能正确显示足以说明这一点。 -
Provider
组件利用Object.is
检测value
prop 的值是否有更新。注意Object.is
和===
的行为不完全相同。具体细节请参考Object.is
的 MDN 文档页。 -
Consumer
组件接收一个函数作为children
prop 并利用该函数的返回值生成组件树的模式被称为 Render Props 模式。详细介绍请参考相关 React 文档
127.在models层获取全局状态树数据
- Redux下:const state =(window as GlobalDefinitions).store.getState().namespaceName
- Dva中: const state = yield select(st => st.namespaceName);
namespaceName为要获取的那个状态树的命名空间
128.antd Table复选框选中控制
选中控制一定要通过selectedRowKeys,不然会出现选中的key是空时,界面上还有勾
rowSelection={{
selectedRowKeys: selectedRows.map(i => i.docItemId),
onChange: this.handleRowSelect,
}}
129. 重新部署后页面js文件报404
现象:react项目发布后, 需要用户手动刷新浏览器才能正常运行。有md5值, js每次更改代码后, 打包出的hash值也是不同的, 但用户必须刷新页面缓存才能正常使用
分析:检查 index.html 有没有在 Nginx 设置缓存;检查项目的 vendor.js common.js 是否有hash。
index.html被缓存了,被缓存的index.html引用的是旧版js、css资源,这些资源也被浏览器缓存过,所以加载的是上次访问的页面。
解决方法:修改下nginx配置就可以了
location / {
index index.html;
if ($uri ~* "html$") {
add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate";
}
}
https://segmentfault.com/q/1010000007320935
130.查找nginx配置文件所在位置
whereis nginx --查看nginx安装路径
which nginx --查看nginx运行路径
/usr/sbin/nginx -t --在知晓了运行路径为/usr/sbin/nginx条件下,查看nginx配置文件的位置。nginx.conf、default.conf
131. 修改本地启动端口
环境变量 PORT,在 compileStartEnv 里面。 如果没有这个文件,就在 package.json 里面
如果需要保存 就是 写在 环境变量的 地方;
如果不需要保存;
直接设置 当前环境变量就好了。
export PORT=8001 && yarn start // linux
set PORT=8001 && yarn start // windows
132. pointer-events: none;
指定在什么情况下 (如果有) 某个特定的图形元素可以成为鼠标事件