1. async/await
async 的作用
async 函数返回的是一个 Promise 对象
如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象,直接可以用then() 链来处理这个 Promise 对象
如果 async 函数没有返回值,会返回 Promise.resolve(undefined)。
await 到底在等啥
await 只能出现在 async 函数中
await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果
举例
// APP中获取did
const getAppDid = () => new Promise((resolve, reject) => {
if (window.self !== window.top) {
if (appDid) {
resolve(appDid);
} else {
clearTimeout(timer);
timer = setTimeout(() => {
if (appDid) {
resolve(appDid);
} else {
reject();
}
}, 200);
}
return;
}
});
static async follow(token, rid) {
const did = await getAppDid();
return new Promise((resolve, reject) => {
getAppCsrf(() => {
ajaxFn({
url: config.follow,
type: 'POST',
data: {
rid,
did,
token,
[$SYS.tn]: mCookie.getItem($SYS.tvk)
},
success: (res) => {
resolve(res);
},
error: () => {
reject();
}
});
});
});
}
2. redux-thunk 和 redux-observable
自动响应我们所dispatch的actions并执行对应的函数,把复杂的异步函数分离到一些Epic的函数里面
redux-observable 将从 ui 触发的 action 转化为一个数据流,并且订阅它。当数据流有数据发出时,这个流的数据管道中设置了对此数据流做的一系列的操作符( rxjs 提供),数据流通过管道后,将最终的流转成 action
Epics函数
传进一个action,然后return一个新的、不一样的action
这个Epics函数是一个发射器,因此我们能够subscribe它,从而可以监听并收到新的action
每当我们调用redux的dispatch的时候,所有的Epics函数都会执行
import { Observable } from 'rxjs/Rx';
import { ajax } from 'rxjs/observable/dom/ajax';
import * as actions from '../actions';
// action将封装成Observable对象传入到函数内
function fetch(action$) {
// 使用ofType方法匹配相应的action
return action$.ofType('GET_DATA')
// switchMap 是一个高阶的操作符,它一般用在 ajax 网络服务请求上,主要处理多个内部 Observable 对象产生并发的情况下,只订阅最后一个数据源,其他的都退订,这样的操作符,非常适合网络请求
.switchMap(action => {
// ajax将从一个ajax请求中创建一个Observable,并且返回请求的结果
return ajax({
url: 'path/',
method: 'GET',
responseType: 'json'
})
// map/filter方法和数组的相应方法类似,可以用来对返回结果进行处理
.map(res => res.response || res)
.map(res => {
console.log(res);
if (res.error) {
throw new Error(res.error);
} else if (res.results) {
return res.results;
} else {
throw new Error('invalid response');
}
})
// 当结果处理完成后,就可以将其作为参数交给下一个action:reveiveData进行dispatch。
.map(actions.fetchReceive)
// 如果有错误的话,就交给相应的action来处理。在这里,这个action必须使用Observable.of方法转为一个observable
.catch(error => Observable.of(actions.createError(error)));
});
}
// 引入combineEpics方法
import { combineEpics } from 'redux-observable';
// combineEpics会将参数中的epic函数合并到一起
export default combineEpics(fetch);
3.react-redux 的 mapStateToProps/mapDispatchToProps
React-redux mapStateToProps,mapDispatchToProps 如何使用
Provider
首先在最外层容器中,把所有内容包裹在 Provider 组件中,将之前创建的 store 作为 prop 传给 Provider
Provider 内的任何一个组件(比如这里的 Comp ),如果需要使用 state 中的数据,就必须是「被 connect 过的」
const App = () => {
return (
<Provider store={store}>
<Comp/>
</Provider>
)
};
connect
connect() 接收四个参数,它们分别是 mapStateToProps , mapDispatchToProps , mergeProps 和 options
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
mapStateToProps
允许我们将 store 中的数据作为 props 绑定到组件上
第一个参数就是 Redux 的 store ,可以从中摘取属性,可以不必将 state 中的数据原封不动地传入组件,根据 state 中的数据动态地输出组件需要的属性
第二个参数 ownProps ,是 MyComp 自己的 props
当 state 变化,或者 ownProps 变化的时候, mapStateToProps 都会被调用,计算出一个新的 stateProps ,(在与 ownProps merge 后)更新给 MyComp
const mapStateToProps = (state, ownProps) => {
// state 是 {userList: [{id: 0, name: '王二'}]}
return {
user: _.find(state.userList, {id: ownProps.userId}),
greaterThanFive: state.count > 5
}
}
mapDispatchToProps
将 action 作为 props 绑定到 MyComp 上
// 由于 mapDispatchToProps 方法返回了具有 increase 属性和 decrease 属性的对象,这两个属性也会成为 MyComp 的 props
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increase: (...args) => dispatch(actions.increase(...args)),
decrease: (...args) => dispatch(actions.decrease(...args))
}
}
为了不让 MyComp 组件感知到 dispatch 的存在,我们需要将 increase 和 decrease 两个函数包装一下,使之成为直接可被调用的函数(即,调用该方法就会触发 dispatch )
Redux 本身提供了 bindActionCreators 函数,来将 action 包装成直接可被调用的函数
import {bindActionCreators} from 'redux';
const mapDispatchToProps = (dispatch, ownProps) => {
return bindActionCreators({
increase: action.increase,
decrease: action.decrease
});
}
mergeProps
不管是 stateProps 还是 dispatchProps ,都需要和 ownProps merge 之后才会被赋给 MyComp 。 connect 的第三个参数就是用来做这件事。通常情况下可以不传这个参数
举例
const mapStateToProps = state => ({
showSupportTeams: state.showSupportTeams,
mainNavTabIndex: state.mainNavTabIndex,
needRefreshUserInfo: state.needRefreshUserInfo
});
const mapDispatchToProps = dispatch => ({
setMainNavTabIndex: (payload) => {
dispatch(Actions.setMainNavTabIndex(payload));
},
setLoginStatus: (payload) => {
dispatch(Actions.setLoginStatus(payload));
},
setBasicConfigInformation: (payload) => {
dispatch(Actions.setBasicConfigInformation(payload));
}
});
export default connect(mapStateToProps, mapDispatchToProps)(HomePage);
4. react-router-dom
使用React构建的单页面应用,使用路由实现页面跳转。在React中,常用的包可以实现这个需求,包括react-router、react-router-dom、react-router-native
涉及
- Router: HashRouter/BrowserRouter
- Histories: hashHistory/browserHistory
- Route 和 Link
- Switch
举例
import { Provider } from 'react-redux';
import ReactDOM from 'react-dom';
import { HashRouter, Route, Switch, hashHistory } from 'react-router-dom';、
import store from './store';
import Home from './pages/Home';
import LevelDetils from './pages/LevelDetails';
import AllRecords from './pages/AllRecords';
ReactDOM.render((
<Provider store={store}>
<HashRouter history={hashHistory}>
<Switch>
<Route exact path='/' exact component={Home} />
<Route exact path='/leveldetail' exact component={LevelDetils} />
<Route exact path='/allrecords' exact component={AllRecords} />
</Switch>
</HashRouter>
</Provider>
), document.getElementById('root'));
5. csrf验证
CSRF攻击的全称是跨站请求伪造( cross site request forgery),是一种对网站的恶意利用,CSRF则是通过伪装来自受信任用户的请求来利用受信任的网站。你可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义向第三方网站发送恶意请求。 CRSF能做的事情包括利用你的身份发邮件、发短信、进行交易转账等,甚至盗取你的账号。
CSRF攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于cookie中,因此攻击者可以在不知道用户验证信息的情况下直接利用用户的cookie来通过安全验证。由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信总不存在于cookie之中。鉴于此,系统开发人员可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务端进行token校验,如果请求中没有token或者token内容不正确,则认为是CSRF攻击而拒绝该请求。
postWish(content) {
return of({ content, rid: this.roomId }).pipe(
switchMap(payload => getCSRFCookie(payload)),
switchMap(({ name, value, payload }) => httpClient.post(
String,
this.config.postWish,
{
content: payload.content,
room_id: payload.rid,
[name]: value
},
{ headers: { 'content-type': 'application/x-www-form-urlencoded' } }
)),
map((res) => {
}),
);
}
6. CSS3帧动画
核心的是利用CSS3中Animation动画,确切的说是使用animation-timing-function 的阶梯函数 steps(number_of_steps, direction) 来实现逐帧动画的连续播放
连续切换雪碧图位置
将所有的帧动画图片合并成一张雪碧图,通过改变 background-position 的值来实现动画帧切换
使用steps阶梯函数切换雪碧图位置
(1)写法一
详细定义了一个关键帧周期,从开始到结束,每两个关键帧之间分 1 步展示完,也就是说0% ~ 5%之间变化一次,5% ~ 10%变化一次
.sprite {
width: 300px;
height: 300px;
background-repeat: no-repeat;
background-image: url(frame.png);
animation: frame 333ms steps(1,end) both infinite;
}
@keyframes frame {
0% {background-position: 0 0;}
5% {background-position: -300px 0;}
10% {background-position: -600px 0;}
15% {background-position: -900px 0;}
20% {background-position: -1200px 0;}
25% {background-position: -1500px 0;}
30% {background-position: -1800px 0;}
35% {background-position: -2100px 0;}
40% {background-position: -2400px 0;}
45% {background-position: -2700px 0;}
50% {background-position: -3000px 0;}
55% {background-position: -3300px 0;}
60% {background-position: -3600px 0;}
65% {background-position: -3900px 0;}
70% {background-position: -4200px 0;}
75% {background-position: -4500px 0;}
80% {background-position: -4800px 0;}
85% {background-position: -5100px 0;}
90% {background-position: -5400px 0;}
95% {background-position: -5700px 0;}
100% {background-position: -6000px 0;}
}
(2) 写法二
定义了关键帧的开始和结束,也就是定义了一个关键帧周期,但因为我们没有详细的定义每一帧的展示,所以我们要将0%~100%这个区间分成20步来阶段性展示
.sprite {
width: 300px;
height: 300px;
background-repeat: no-repeat;
background-image: url(frame.png);
animation: frame 333ms steps(20) both infinite;
}
@keyframes frame {
0% {background-position: 0 0;}//可省略
100% {background-position: -6000px 0;}
}
7. React.createRef() 和 refs
可以通过React.createRef()创建Refs并通过ref属性联系到React组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
当一个ref通过render放入一个元素中,一个对节点的引用可以通过ref的current属性得到
const node = this.myRef.current;
Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。
我们可以为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回:
class UnControlledForm extends Component {
handleSubmit = () => {
console.log("Input Value: ", this.input.value)
}
render () {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
ref={(input) => this.input = input} />
<button type='submit'>Submit</button>
</form>
)
}
}
上述代码中的 input 域包含了一个 ref 属性,该属性声明的回调函数会接收 input 对应的 DOM 元素,我们将其绑定到 this 指针以便在其他的类函数中使用。
参考: https://blog.csdn.net/u013637262/article/details/80336992
8. React.createPortal
一般使用 React 的组件都是挂到父组件的 this.props.children 上面,总是被最近的父组件所捕获,最终到 React 根组件上。而 Protals 则提供了一种将组件直接挂载到直接父组件 DOM 层次之外的一类方式。
react-dom 提供的具体方法是 ReactDOM.createPortals(child, container),这个方法需要两个参数,第一个参数是需要挂载的组件实例,而第二个参数则是要挂载到的DOM节点。
portal 一个典型的用法是,当父组件有 overflow: hidden 或者 z-index 样式时,但是子组件需要“打破”父组件容器,显示在父组件之外。比如 dialogs,hovercards,tooltips 组件。
虽然 portal 可以在 DOM 树中的任意位置,但是它的行为依旧和普通的 React child 一样。比如上下文环境完全一样,无论 child 是不是 portal; portal 也一直存在于在 React 树上,无论它位于 DOM 树中的什么位置。包括,事件冒泡。portal 节点的事件会冒泡到它的 React 树的祖先节点上,即使这些 React 树上的祖先节点并不是 DOM 树上的祖先节点
https://segmentfault.com/a/1190000012325351
render() {
const qrCodeUrl = `https://wxapp.douyucdn.cn/wxmpdouble11?rid=${this.roomId}&from=web`;
const {
bgImg
} = this.state;
return ReactDOM.createPortal((
<div className="Double11Popup" style={{ backgroundImage: `url(${bgImg})` }}>
<div className="Double11Popup-QRcode">
<QRCode
value={qrCodeUrl}
size={121}
/>
</div>
<div className="Double11Popup-close" onClick={this.clickClosePopup}></div>
</div>
), document.body);
}
9. bind(this)
class Foo extends React.Component {
handleClick () {
this.setState({ xxx: aaa })
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
这样会报 this 是 undefined 的错,需要在constuctor中this.handleClick = this.handleClick.bind(this),原因如下
jsx
jsx 实际上是 React.createElement(component, props, …children) 函数提供的语法糖,那么这段 jsx 代码:
<button onClick={this.handleClick}>
Click me
</button>
会被转化为:
React.createElement("button", {
onClick: this.handleClick
}, "Click me")
JavaScript自身特性
如果传递一个函数名给一个变量,之后通过函数名()的方式进行调用,在方法内部如果使用this则this的指向会丢失。
const test = {
name:'jack',
getName:function(){
console.log(this.name)
}
}
test.getName() // jack
const func = test.getName;
func(); // undefined
React中的bind同上方原理一致,在JSX中传递的事件不是一个字符串,而是一个函数(如:onClick={this.handleClick}),此时onClick即是中间变量,所以处理函数中的this指向会丢失。解决这个问题就是给调用函数时bind(this),从而使得无论事件处理函数如何传递,this指向都是当前实例化对象。