yuthelloworld / blog Goto Github PK
View Code? Open in Web Editor NEW🌎 技术自留地
🌎 技术自留地
今年3月初发布了react-router v4,相较之前的v3和v2版本做了一个破坏性的升级。遵循一切皆React Component的理念。静态路由变成了动态路由。这里记录下v3项目如何迁移到v4。
项目地址:https://github.com/YutHelloWorld/vortex-react
详细代码参阅这个PR
这里我们仍然不使用
react-router-redux
这个库。为了和react-router
v4版本保持一致,react-router-redux
发布了v5.0.0版本,你当然也可以使用它来实现这个功能。
v3我们引入的是react-router
包,在v4我们只引入react-router-dom
这个包。安装react-router-dom
时会同时安装history
。
package.json
- "react-router": "^3.0.0",
+ "react-router-dom": "^4.1.2",
location.js
// v3
import { browserHistory } from 'react-router'
// 获取当前location
const initialState = browserHistory.getCurrentLocation()
==>
// v4
import createHistory from 'history/createBrowserHistory'
export const history = createHistory()
// Get the current location.
const initialState = history.location
这里替换的是history,和当前location的获取方法。在v3,browserHistory存在于react-router
中,而v4把history抽离了出来,提供了createBrowserHistory
,createHashHistory
,createMemoryHistory
三种创建history的方法。v4中创建的history导出,在后面会需要用到。
history
API详见: https://github.com/ReactTraining/history
createStore
// v3
import { browserHistory } from 'react-router'
import { updateLocation } from './location'
store.unsubscribeHistory = browserHistory.listen(updateLocation(store))
updateLocation
用来把location的更新同步到store中。
export const updateLocation = ({ dispatch }) => {
return (nextLocation) => dispatch(locationChange(nextLocation))
}
一切似乎都很顺利,接着第一个坑来了
根据history
API提供的
// Listen for changes to the current location.
const unlisten = history.listen((location, action) => {
// location is an object like window.location
console.log(action, location.pathname, location.state)
})
修改createStore.js
==>
// v4
import { updateLocation, history } from './location'
// 监听浏览器history变化,绑定到store。取消监听直接调用store.unsubscribeHistory()
store.unsubscribeHistory = history.listen(updateLocation(store))
接着修改app.js
// v3
// ...
import { browserHistory, Router } from 'react-router'
// ...
<Router history={browserHistory} children={routes} />
==>
// ...
import { BrowserRouter, Route } from 'react-router-dom'
// ...
<BrowserRouter>
<div>
<Route path='/' component={CoreLayout} />
</div>
</BrowserRouter>
//...
我们到浏览器中查看,发现URL变化并没有触发updateLocation(store)
,state并没有变化。
What a f**k!
问题出在BrowserRouter
在创建的时候在内部已经引入了一个history
,updateLocation(store)
应该监听的是内部的这个history
。这里贴下BrowserRouter.js的代码
import React from 'react'
import PropTypes from 'prop-types'
import createHistory from 'history/createBrowserHistory'
import { Router } from 'react-router'
/**
* The public API for a <Router> that uses HTML5 history.
*/
class BrowserRouter extends React.Component {
static propTypes = {
basename: PropTypes.string,
forceRefresh: PropTypes.bool,
getUserConfirmation: PropTypes.func,
keyLength: PropTypes.number,
children: PropTypes.node
}
history = createHistory(this.props)
render() {
return <Router history={this.history} children={this.props.children}/>
}
}
export default BrowserRouter
于是,我们放弃使用BrowserRouter
,而使用Router
。
修改app.js
==>
// v4
import { Router, Route } from 'react-router-dom'
//...
<Router history={history}>
<div>
<Route path='/' component={CoreLayout} />
</div>
</Router>
这样,这个坑算是填上了。也就完成了history和store之间的同步。
v4取消了PlainRoute 中心化配置路由。
Route
是一个react component。
取消了IndexRoute
,通过Switch
来组件提供了相似的功能,当<Switch>
被渲染时,它仅会渲染与当前路径匹配的第一个子<Route>
。
routes/index.js
// v3
//..
export const createRoutes = (store) => ({
path : '/',
component : CoreLayout,
indexRoute : Home,
childRoutes : [
CounterRoute(store),
ZenRoute(store),
ElapseRoute(store),
RouteRoute(store),
PageNotFound(),
Redirect
]
})
//...
==>
// ...
const Routes = () => (
<Switch>
<Route exact path='/' component={Home} />
<Route path='/counter' component={AsyncCounter} />
<Route path='/zen' component={AsyncZen} />
<Route path='/elapse' component={AsyncElapse} />
<Route path='/route/:id' component={AsyncRoute} />
<Route path='/404' component={AsyncPageNotFound} />
<Redirect from='*' to='/404' />
</Switch>
)
export default Routes
//
这里路由的定义方式由PlainRoute Object改写成了组件嵌套形式,在PageLayout.js
中插入<Routes />
。
v3版本通过
getComponet
和require.ensure
实现代码分割和动态路由。在v4版本,我们新增异步高阶组件,并使用import()
替代require.ensure()
Counter/index.js
// v3
import { injectReducer } from '../../store/reducers'
export default (store) => ({
path : 'counter',
/* 动态路由 */
getComponent (nextState, cb) {
/* 代码分割 */
require.ensure([], (require) => {
const Counter = require('./containers/CounterContainer').default
const reducer = require('./modules/counter').default
/* 将counterReducer注入rootReducer */
injectReducer(store, { key : 'counter', reducer })
cb(null, Counter)
}, 'counter')
}
})
首先,新增AsyncComponent.js
import React from 'react'
export default function asyncComponent (importComponent) {
class AsyncComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
component: null,
}
}
async componentDidMount () {
const { default : component } = await importComponent()
this.setState({
component: component
})
}
render () {
const C = this.state.component
return C
? <C {...this.props} />
: null
}
}
return AsyncComponent
}
- 这个
asyncComponent
函数接受一个importComponent
的参数,importComponent
调用时候将动态引入给定的组件。- 在
componentDidMount
我们只是简单地调用importComponent
函数,并将动态加载的组件保存在状态中。- 最后,如果完成渲染,我们有条件地提供组件。在这里我们如果不写null的话,也可提供一个菊花图,代表着组件正在渲染。
接着,改写Counter/index.js
==>
import { injectReducer } from '../../store/reducers'
import { store } from '../../main'
import Counter from './containers/CounterContainer'
import reducer from './modules/counter'
injectReducer(store, { key : 'counter', reducer })
export default Counter
一旦加载
Counter/index.js
,就会把counterReducer注入到Rudecer中,并加载Counter组件。
v4 移除了
onEnter
onLeave
等属性,history
替换router
属性,新增match
this.props.router.push('/')
==>
this.props.history.push('/')
this.props.params.id
==>
this.props.match.params.id
这里可以看出,使用v4替换v3,对于大型项目并不是一件轻松的事情,有许多小坑要踩,这就是社区很多项目仍然使用v2/v3的原因。笔者认为,v4更符合React的组件**,于是做了一个实践。最后欢迎指正拍砖,捂脸求star 🤣 。
假定当前日期:2017/8/15
var today = new Date()
console.log(today.getYear()+1900)
// 2017
var today = new Date()
console.log(today.getMonth())
// 7
var today = new Date()
var lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0)
console.log(lastDay)
// 输出:Thu Aug 31 2017 00:00:00 GMT+0800 (CST)
date是从1开始计的,0就是上月的最后一天。
var aDate = new Date(2017, 1, 31)
console.log(aDate)
// 输出:Fri Mar 03 2017 00:00:00 GMT+0800 (CST)
date会自动往后推算
业务上常常会需要把Date转换成自定义的日期字符串。
这里介绍个日期格式化的工具: taylorhakes/fecha/
fecha.format(new Date(), 'YYYY年MM月DD日')
// 2017年09月06日
-- React Redux 数据流
通过这张流程图,我们可以更好的理解Redux和React直接数据如何流通,关系如何映射。
让我们一步步来了解图中的各个概念。
action creator 就是函数而已,负责构建一个 action (是的,action creator 这个名字已经很明显了)并返回它。通过几行简单的代码就可以解释清楚了!
const actionCreator = function () {
return {
type : 'AN_ACTION'
}
}
一般约定 action 是一个拥有 type 属性的对象。
console.log(actionCreator())
// { type: 'AN_ACTION' }
Reducer 函数只是一个纯函数,它接收应用程序的当前状态以及发生的 action,然后返回修改后的新状态(或者有人称之为归并后的状态)。Reducer 函数是 action 的订阅者。
const reducer = function (state = {}, action) {
console.log('reducer was called with state', state, 'and action', action);
return state;
}
以上,action描述“发生了什么”,而reducer根据action来更新state。但是他们两者之间是如何关联的呢?
不用担心,Redux 会帮你把action和reducer连接起来。
我们把 Redux实例称为 store
并用以下方式创建:
import { createStore } from 'redux'
const store_0 = createStore(() => {})
注意:在createStore时,需要给它传入一个 reducer 函数。
每当一个action发生时,Redux都能调用这个函数。往 createStore 传 Reducer 的过程就是给 Redux绑定 action处理函数(也就是Reducer)的过程。
接下来,试着在 Reducer 中打印一些 log
const reducer = function (...args) {
console.log('Reducer was called with args', args)
}
const store_1 = createStore(reducer)
// 输出:Reducer was called with args [ undefined, { type: '@@redux/INIT' } ]
我们没有dispatch(分发)任何action,但是reducer被调用了!这是由于初始化应用state的时候,Redux dispatch 了一个初始化的 action ({ type: '@@redux/INIT' })
。reducer的入参为(state, action)
。state还没有被初始化,自然为undefined
。
如何读取store中的state?
Redux为我们提供了store.getState()
方法。
import { createStore } from 'redux'
const reducer_2 = function (state = {}, action) {
console.log('reducer_2 was called with state', state, 'and action', action)
return state;
}
const store_2 = createStore(reducer_2)
// 输出: reducer_2 was called with state {} and action { type: '@@redux/INIT' }
console.log('store_2 state after initialization:', store_2.getState())
// 输出: store_2 state after initialization: {}
如何dispatch action?
我们需要使用store.dispatch(action)
方法。
// 接以上代码
const anAction = {
type : 'AN_ACTION'
}
store_2.dispatch(anAction);
// 输出:reducer_2 was called with state {} and action { type: 'AN_ACTION' }
combineReducer
用于合并Reducers,并且合并对应的State。
const userReducer = function (state = {}, action) {
console.log('userReducer was called with state', state, 'and action', action)
switch (action.type) {
// etc.
default:
return state;
}
}
const itemsReducer = function (state = [], action) {
console.log('itemsReducer was called with state', state, 'and action', action)
switch (action.type) {
// etc.
default:
return state;
}
}
import { createStore, combineReducers } from 'redux'
const reducer = combineReducers({
user : userReducer,
items : itemsReducer
})
// 输出:
// userReducer was called with state {} and action { type: '@@redux/INIT' }
// userReducer was called with state {} and action { type: '@@redux/PROBE_UNKNOWN_ACTION_9.r.k.r.i.c.n.m.i' }
// itemsReducer was called with state [] and action { type: '@@redux/INIT' }
// itemsReducer was called with state [] and action { type: '@@redux/PROBE_UNKNOWN_ACTION_4.f.i.z.l.3.7.s.y.v.i' }
var store_0 = createStore(reducer)
// 输出:
// userReducer was called with state {} and action { type: '@@redux/INIT' }
// itemsReducer was called with state [] and action { type: '@@redux/INIT' }
console.log('store_0 state after initialization:', store_0.getState())
// 输出:
// store_0 state after initialization: { user: {}, items: [] }
View组件通过click等事件,dispatch一个(actionCreator返回的)action,通过Store把当前状态state和action传递给订阅者reducer函数,reducer返回一个新的状态存储在Store中,Store又把新的State传递给View组件触发组件更新。
为了将Redux和React联系到一起。就需要用到React-Redux
这个库。
import { connect } from 'react-redux'
const containerComponent = connect(mapStateToProps, mapDispatchToProps)(presentationalComponent)
简单来说,mapStateToProps和mapDispatchToProps就是分别把Redux的state,和dispatch(action)映射到React组件中作为props。connect
将展示组件(presentationalComponent)封装成高阶的容器组件(containerComponent)。state的更新意味着props更新。
/**
* 解析一个url并生成window.location对象中包含的域
* location:
* {
* href: '包含完整的url',
* origin: '包含协议到pathname之前的内容',
* protocol: 'url使用的协议,包含末尾的:',
* username: '用户名', // 暂时不支持
* password: '密码', // 暂时不支持
* host: '完整主机名,包含:和端口',
* hostname: '主机名,不包含端口'
* port: '端口号',
* pathname: '服务器上访问资源的路径/开头',
* search: 'query string,?开头',
* hash: '#开头的fragment identifier'
* }
*
* @param {string} url 需要解析的url
* @return {Object} 包含url信息的对象
*/
function parseUrl(url) {
var result = {};
var keys = ['href', 'origin', 'protocol', 'host',
'hostname', 'port', 'pathname', 'search', 'hash'];
var i, len;
var regexp = /(([^:]+:)\/\/(([^:/?#]+)(:\d+)?))(\/[^?#]*)?(\?[^#]*)?(#.*)?/;
var match = regexp.exec(url);
console.info('match=', match);
if (match) {
for (i = keys.length - 1; i >= 0; --i) {
result[keys[i]] = match[i] ? match[i] : '';
}
}
console.info('result=', result);
return result;
}
parseUrl("http://test.com:8080?name=1&password=2#page1");
结果:
match=[
'http://test.com:8080?name=1&password=2#page1',
'http://test.com:8080',
'http:',
'test.com:8080',
'test.com',
':8080',
undefined,
'?name=1&password=2',
'#page1',
index: 0,
input: 'http://test.com:8080?name=1&password=2#page1'
]
result={
hash: '#page1',
search: '?name=1&password=2',
pathname: '',
port: ':8080',
hostname: 'test.com',
host: 'test.com:8080',
protocol: 'http:',
origin: 'http://test.com:8080',
href: 'http://test.com:8080?name=1&password=2#page1'
}
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
gzip on;
gzip_comp_level 6;
gzip_types text/plain application/javascript text/css application/xml text/javascript;
server {
listen 8086;
server_name localhost;
location / {
root /home/tsycl/lib;
index /html/index.html;
try_files $uri $uri/ /html/$uri.html =404;
}
location /api {
proxy_pass http://abc.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
前端静态资源
lib
|- html
|- index.html
|- demo.html
|- js
|- css
post
/api/a 会被转发到http://abc.com/api/a
添加一个环境变量即可
vim ~/.bashrc
然后最后新增一行:
export NVM_NODEJS_ORG_MIRROR=https://cdn.npmmirror.com/binaries/node
最后保存 :wq
使之生效
source ~/.bashrc
修改setting文件
node_mirror: https://npmmirror.com/mirrors/node/
npm_mirror: https://npmmirror.com/mirrors/npm/
本文介绍一些日常使用WebStorm的设置和快捷键。欢迎补充,互惠你我。🙃
版本: WebStorm 2017.2
操作系统:Mac OS
直接克隆这个仓库导入使用:https://github.com/YutHelloWorld/WebStrom-Setting
tically if application is idle for"
首先确保安装了Nodejs, 并且工程中配置了Eslint相关文件:
.eslintrc
、.eslinignore
Preferences > Code Style
分别设置各个类型文件的代码风格。
这里快捷键选用的是Mac OS
command + shift + L
command + 鼠标左键
option + 鼠标左键
收藏快捷键:将光标移动到目标行,按快捷键 F3
跳转至收藏的行: command+F3
高级
给收藏处标序号:option + F3
command + L
command + shift + o
以上只是很小的一些设置技巧,欢迎补充😆
block 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。
none 缺省值。象行内元素类型一样显示。
inline 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。
inline-block 默认宽度为内容宽度,可以设置宽高,同行显示。
list-item 像块类型元素一样显示,并添加样式列表标记。
table 此元素会作为块级表格来显示。
inherit 规定应该从父元素继承 display 属性的值。
(1)有两种, IE 盒子模型、W3C 盒子模型;
(2)盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border);
(3)区 别: IE 的content部分把 border 和 padding计算了进去;
div{
width: 200px;
margin: 0 auto;
}
div {
position: absolute;
width: 300px;
height: 300px;
margin: auto;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: pink; /* 方便看效果 */
}
/* 确定容器的宽高 宽500 高 300 的层,设置层的外边距 */
div {
position: relative; /* 相对定位或绝对定位均可 */
width:500px;
height:300px;
top: 50%;
left: 50%;
margin: -150px 0 0 -250px; /* 外边距为自身宽高的一半 */
background-color: pink; /* 方便看效果 */
}
/* 未知容器的宽高,利用 `transform` 属性 */
div {
position: absolute; /* 相对定位或绝对定位均可 */
width:500px;
height:300px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: pink; /* 方便看效果 */
}
/* 利用 flex 布局,实际使用时应考虑兼容性 */
.container {
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
}
.container div {
width: 100px;
height: 100px;
background-color: pink; /* 方便看效果 */
}
将多个元素设置为同一行:float,inline-block
清除浮动的方式:方法一:添加新的元素 、应用 clear:both;
方法二:父级div定义 overflow: hidden;
方法三:利用:after和:before来在元素内部插入两个元素块,从面达到清除浮动的效果。
.clear{zoom:1;}
.clear:after{content:””;clear:both;display:block;height:0;overflow:hidden;visibility:hidden;}
ajax是宏任务还是微任务?
原文:Our Best Practices for Writing React Components .
这里意译。有些点在之前的文章里提到过:#2
如果组件带有state
或者方法,就使用Class写法。
import React, { Component } from 'react'
import { observer } from 'mobx-react'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
使用ES7句法定义state
import React, { Component } from 'react'
import { observer } from 'mobx-react'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
export default class ProfileContainer extends Component {
state = { expanded: false }
propTypes
和defaultProps
import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
export default class ProfileContainer extends Component {
state = { expanded: false }
static propTypes = {
model: object.isRequired,
title: string
}
static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}
propTypes
和defaultProps
的声明应该置顶便于其他开发者阅读。在React v15.3.0版本,推荐使用prop-types这个包替代React.PropTypes。
重要的一点:所有的组件都应当有propTypes验证。
import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
export default class ProfileContainer extends Component {
state = { expanded: false }
static propTypes = {
model: object.isRequired,
title: string
}
static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}
handleSubmit = (e) => {
e.preventDefault()
this.props.model.save()
}
handleNameChange = (e) => {
this.props.model.changeName(e.target.value)
}
handleExpand = (e) => {
e.preventDefault()
this.setState({ expanded: !this.state.expanded })
}
在方法中使用箭头函数来替代this.handleExpand.bind(this)
this.setState()
是异步的。应该使用函数入参this.setState(prevState => ({ expanded: !prevState.expanded }))
props
应当分行写render() {
const {
model,
title
} = this.props
return (
<ExpandableForm
onSubmit={this.handleSubmit}
expanded={this.state.expanded}
onExpand={this.handleExpand}>
<div>
<h1>{title}</h1>
<input
type="text"
value={model.name}
onChange={this.handleNameChange}
placeholder="Your Name"/>
</div>
</ExpandableForm>
)
}
<input
type="text"
value={model.name}
// onChange={(e) => { model.name = e.target.value }}
// ^ Not this. Use the below:
onChange={this.handleChange}
placeholder="Your Name"/>
class
组件写法import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { string, object } from 'prop-types'
// 分开本地导入和依赖导入
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'
// 使用修饰器(如果有的话)
@observer
export default class ProfileContainer extends Component {
state = { expanded: false }
// 初始化state (ES7) 或者在构造函数(constructor)中初始化state (ES6)
//使用静态属性(ES7)声明propTypes越早越好
static propTypes = {
model: object.isRequired,
title: string
}
// 在propTypes后声明defaultProps
static defaultProps = {
model: {
id: 0
},
title: 'Your Name'
}
// 使用箭头函数绑定指向定义的上下文的this
handleSubmit = (e) => {
e.preventDefault()
this.props.model.save()
}
handleNameChange = (e) => {
this.props.model.name = e.target.value
}
handleExpand = (e) => {
e.preventDefault()
this.setState(prevState => ({ expanded: !prevState.expanded }))
}
render() {
// 解构props成可读的
const {
model,
title
} = this.props
return (
<ExpandableForm
onSubmit={this.handleSubmit}
expanded={this.state.expanded}
onExpand={this.handleExpand}>
// 如果有2个以上props,分行写
<div>
<h1>{title}</h1>
<input
type="text"
value={model.name}
// onChange={(e) => { model.name = e.target.value }}
// 避免创造新的闭包,应该使用下面的方法。
onChange={this.handleNameChange}
placeholder="Your Name"/>
</div>
</ExpandableForm>
)
}
}
import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
import './styles/Form.css'
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool
}
// Component declaration
import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
import './styles/Form.css'
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
}
function ExpandableForm(props) {
const formStyle = props.expanded ? {height: 'auto'} : {height: 0}
return (
<form style={formStyle} onSubmit={props.onSubmit}>
{props.children}
<button onClick={props.onExpand}>Expand</button>
</form>
)
}
结合ES6的函数入参解构,可以如下书写
import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
import './styles/Form.css'
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
}
function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
const formStyle = expanded ? {height: 'auto'} : {height: 0}
return (
<form style={formStyle} onSubmit={onSubmit}>
{children}
<button onClick={onExpand}>Expand</button>
</form>
)
}
const ExpandableForm = ({ onExpand, expanded, children }) => {
虽然语法没问题,但是这里函数是匿名函数。
import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
import './styles/Form.css'
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
}
function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
const formStyle = expanded ? {height: 'auto'} : {height: 0}
return (
<form style={formStyle} onSubmit={onSubmit}>
{children}
<button onClick={onExpand}>Expand</button>
</form>
)
}
export default observer(ExpandableForm)
import React from 'react'
import { observer } from 'mobx-react'
import { func, bool } from 'prop-types'
// Separate local imports from dependencies
import './styles/Form.css'
// 在组件前声明propTypes
ExpandableForm.propTypes = {
onSubmit: func.isRequired,
expanded: bool,
onExpand: func.isRequired
}
// 解构props,通过函数入参默认值的方式设定defaultProps
function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
const formStyle = expanded ? { height: 'auto' } : { height: 0 }
return (
<form style={formStyle} onSubmit={onSubmit}>
{children}
<button onClick={onExpand}>Expand</button>
</form>
)
}
// Wrap the component instead of decorating it
export default observer(ExpandableForm)
每当我开始一个新项目时,首要任务就是打磨 CSS 语言中的一些粗糙边缘。我使用一组功能性的自定义基线样式来做到这一点。
很长一段时间,我使用 Eric Meyer 著名的CSS Reset。这是 一大块CSS代码,但它有点长;它已经十多年没有更新了,从那以后CSS发生了很多变化!
最近,我一直在使用自己的自定义 CSS 重置。它包括我发现的所有改善用户体验和 CSS 创作体验的小技巧。
像其他 CSS 重置一样,它在设计/样式方面是无主见的。无论您追求何种美感,您都可以将此重置用于任何项目。
在本教程中,我们将参观我的自定义 CSS 重置。我们将深入研究每条规则,您将了解它的作用以及您可能想要使用它的原因!
事不宜迟,下面是代码:
/*
1. 使用更直观的 box-sizing 模型
*/
*, *::before, *::after {
box-sizing: border-box;
}
/*
2. 删除默认边距
*/
* {
margin: 0;
}
/*
3. 在应用程序中允许基于百分比的高度
*/
html, body {
height: 100%;
}
/*
排版调整!
4.添加可访问的行高
5. 改善文字渲染
*/
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/*
6. 修改媒体默认值
*/
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
/*
7. 删除内置表单的排版样式
*/
input, button, textarea, select {
font: inherit;
}
/*
8. 避免文本溢出
*/
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
/*
9. 创建根堆叠上下文
*/
#root, #__next {
isolation: isolate;
}
它相对较短,但是这个小样式表中包含了很多东西。让我们开始吧!
一个迂腐的笔记
从历史上看,CSS 重置的主要目标是确保浏览器之间的一致性,并撤消所有默认样式,创建一个空白板。我的 CSS 重置并没有真正做这些事情。
如今,浏览器在布局或间距方面并没有太大的差异。总的来说,浏览器忠实地实现了 CSS 规范,并且一切都如你所愿。所以不再那么需要了。
我也不认为有必要去除所有浏览器默认设置。例如,我可能确实希望<em>
设置标签font-style: italic!
我总是可以在各个项目风格中做出不同的设计决策,但我认为剥离常识默认值没有意义。
我的 CSS 重置可能不符合“CSS 重置”的经典定义,但保证了创造性和自由性。
未完。。
先放结论:
箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。
大白话就是往外找父函数,箭头函数的this = 父函数中的this,没有父函数,那么this就是window。
看一段代码
const name = 'window';
const a = {
name: 'a',
sayHello: () => {
console.log(this.name)
},
that: this
}
a.sayHello(); // 输出a ? 错啦,其实输出的是w, 通过a.that就可以验证this到底是谁
这里的箭头函数,也就是sayHello,所在的作用域其实是最外层的js环境,因为没有其他函数包裹;然后最外层的js环境指向的对象是winodw对象,所以这里的this指向的是window对象。
再看一段代码
const name = 'window';
const a = {
name: 'a',
sayHello: function(){
const s = () => console.log(this.name)
return s//返回箭头函数s
}
}
const sayHello = a.sayHello();
sayHello(); // 输出a
箭头函数s 所在的作用域是sayHello,因为sayHello是一个函数,sayHello里的this就是对象a,所以s箭头函数的this就是a。
来看下class
class Person {
constructor(name,age){
this.name = name
this.age = age
}
study = () => {
//study方法放在了哪里?——类的原型对象上,供实例使用
//通过Person实例调用study时,study中的this就是Person实例
console.log(this);
}
}
const p1 = new Person('tom',18)
p1.study() //通过实例调用study方法
const x = p1.study
x()
箭头函数study,所在作用域是class Person,this始终指向的是实例。
StrictMode
是一个用以标记出应用中潜在问题的工具。就像Fragment
,StrictMode
不会渲染任何真实的UI。它为其后代元素触发额外的检查和警告。
注意:
严格模式检查只在开发模式下运行,不会与生产模式冲突。
你可以在应用的任何地方启用严格模式。例如:
import React from 'react';
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}
在上面的例子中,不会对组件Header
、Footer
进行strict mode检查。然而ComponentOne
、ComponentTwo
以及它们所有的后代将被检查。
StrictMode
目前有助于:
如同在博客中阐明的,在异步React应用中使用某些老式的生命周期方法不安全。但是, 如果应用程序使用第三方库, 则很难确保不使用这些生命周期方法。幸运的是, 严格的模式可以帮助解决这个问题!
当启用严格模式, React将编译一个所有使用不安全生命周期组件的列表,并打印一条关于这些组件的警告信息,就像:
以前,React提供了2种方法管理ref:旧式的字符串ref API和回调API。虽然字符串ref API更加方便,但它有些许缺点,因此我们的正式建议是改用回调方式
React 16.3新增了第三种方式, 它提供了字符串 ref 的方便性, 而没有任何缺点:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
render() {
return <input type="text" ref={this.inputRef} />;
}
componentDidMount() {
this.inputRef.current.focus();
}
}
由于新增的对象式refs很大程度上作为字符串ref的替换,因此strict mode现在对字符串ref的用法发出警告。
注意:
除了新的createRef API,回调ref将被继续支持。您不需要在组件中替换回调ref。它们稍微灵活一些, 因此它们将保持为高级功能。
理论上,React在两个阶段起作用:
render
(方法), 然后将结果与上一次渲染进行比较。componentDidMount
和 componentDidUpdate
。提交阶段通常很快,但是渲染可能很慢。因此, 即将出现的异步模式 (默认情况下尚未启用) 将呈现工作分解为片断, 暂停和恢复工作以避免阻止浏览器。这意味着在提交之前, 反应可能不止一次地调用渲染阶段生命周期, 或者它可以在不提交的情况下调用它们 (因为错误或更高的优先级中断)。
渲染阶段的生命周期包括以下class component方法:
constructor
componentWillMount
componentWillReceiveProps
componentWillUpdate
getDerivedStateFromProps
shouldComponentUpdate
render
setState
更新函数 (第一个形参)因为以上方法可能不止一次被调用,所以它们中不包含副作用尤为重要。忽略此规则可能会导致各种问题, 包括内存泄漏和无效的应用程序状态。不幸的是, 很难发现这些问题, 因为它们通常都是不确定的。
严格模式不能自动检测到你的副作用, 但它可以帮助你发现它们, 使其更具确定性。这是通过有意地双调用以下方法来完成的:
constructor
render
setState
更新函数 (第一个形参)getDerivedStateFromProps
注意:
只在开发模式生效。生产模式下不会生命周期不会被双调用。
举个例子,考虑以下代码:
class TopLevelRoute extends React.Component {
constructor(props) {
super(props);
SharedApplicationState.recordEvent('ExampleComponent');
}
}
乍一看, 这段代码似乎没有问题。但是如果 SharedApplicationState.recordEvent
不是幂等, 那么多次实例化此组件可能会导致无效的应用程序状态。这种微妙的 bug 可能不会在开发过程中显现出来, 或者它可能会不一致, 因此被忽略。
通过有意的双调用方法 (如组件构造函数), 严格模式使得这样的行为更容易被发现。
const element = <h1>Hello, world!</h1>;
你可以任意地在 JSX 当中使用 JavaScript 表达式,在 JSX 当中的表达式要包含在大括号里。
<h1>{title}</h1>
如果 JSX 标签是闭合式的,那么你需要在结尾处用 />, 就好像 XML/HTML 一样:
const element = <img src={user.avatarUrl} />;
互相嵌套
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
class
是保留词,所以添加样式时,需用 className
代替 class
。
<h1 className="loading">Hello world</h1>
<div>
{text.map(item => (
<p key={item.id}>{item.text}</p>
))}
</div>
分别是 React.createClass, class 和 Stateless Functional Component(无状态组件)。推荐尽量使用最后一种,保持简洁和无状态。这是函数,不是 Object,没有 this 作用域,是 pure function。
var MyComponent = React.createClass({
componentWillMount: function(){
},
render: function() {
return (
<div>ES5</div>
);
},
});
Warning: Accessing createClass via the main React package is deprecated, and will be removed in React v16.0. Use a plain JavaScript class instead. If you're not yet ready to migrate, create-react-class v15.* is available on npm as a temporary, drop-in replacement. For more info see https://fb.me/react-create-class
createClass写法是React官方反对的,会在React v15.*短期保留,在v16.0版本会被移除。
const MyComponent = () => (
<div>
ES6
</div>
);
class MyComponent extends React.Component {
render() {
return (
<div>ES6</div>
);
}
}
使用PropTypes检查props
ES6写法
import Proptypes from 'prop-types'
class Video extends React.Component {
render() {
return (
<View />
);
}
}
Video.defaultProps = {
autoPlay: false,
maxLoops: 10,
};
Video.propTypes = {
autoPlay: PropTypes.bool.isRequired,
maxLoops: PropTypes.number.isRequired
};
ES 试验特性写法:
static
是类的静态属性,不会被实例继承
class Video extends React.Component {
static defaultProps = {
autoPlay: false,
maxLoops: 10,
}
static propTypes = {
autoPlay: PropTypes.bool.isRequired,
maxLoops: PropTypes.number.isRequired
}
state = {
loopsRemaining: this.props.maxLoops,
}
}
initialState的设定应放在constructor中
export default class Header extends Component {
constructor(props) {
super(props);
this.state = {
title: props.title
};
}
}
也可以按照ES 试验特性写法
export default class Header extends Component {
state = {
title: this.props.title
};
// followed by constructor...
}
class AutoloadingPostsGrid extends React.Component {
render() {
const {
className,
...others, // contains all properties of this.props except for className
} = this.props;
return (
<div className={className}>
<PostsGrid {...others} />
<button onClick={this.handleLoadMoreClick}>Load more</button>
</div>
);
}
}
// with arrow function
const App = ({className, ...rest}) => (
<div className={classnames(className)} {...rest}>
<MyComponent />
</div>
);
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah'
}
};
const dogName = adventurer.dog?.name;
console.log(dogName);
// expected output: undefined
console.log(adventurer.someNonExistentMethod?.());
// expected output: undefined
?.
等价于
let temp = obj.first;
let nestedProp = ((temp === null || temp === undefined) ? undefined : temp.second);
空值运算符:
const foo = null ?? 'default string';
console.log(foo);
// expected output: "default string"
const baz = 0 ?? 42;
console.log(baz);
// expected output: 0
这里梳理下React技术栈需要的最小知识集,让你可以最短时间掌握React,Redux,React-Router,ES6的相关知识,更快的上手React”全家桶“。预计会有ES6、React、Redux、React-Router、Webpack,实时更新目录。
不要用var
,而是用let
和 const
。const
声明一个只读的常量,let
用来声明变量,const
和 let
都是块级作用域。
const PLUS = 'PLUS';
let availableId = 0;
availableId ++;
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
const user = 'world';
console.log(`hello ${user}`); // hello world
// 多行(所有的空格和缩进都会被保留在输出之中)
const content = `
Hello ${firstName},
Thanks for ordering ${qty} tickets to ${event}.
`;
function log(user = 'World') {
console.log(user);
}
log() // World
ES6 允许使用“箭头”(=>)定义函数。
函数的快捷写法,不需要通过 function
关键字创建函数,并且还可以省略 return
关键字。
同时,箭头函数还会继承当前上下文的 this
关键字,即:函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
// ES6
function Timer() {
this.s1 = 0;
setInterval(() => this.s1++, 1000);
}
// 等同于ES5
function Timer() {
this.s1 = 0;
setInterval((function () {
this.s1++;
}).bind(this), 1000);
}
const timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
// s1:3
import
用于引入模块,export
用于导出模块。
//导出默认, counter.js
export default function counter() {
// ...
}
import counter from 'counter';
// 普通导出和导入,reducer.js
export const injectReducer = ( ) => {
//...
}
import { injectReducer } from 'reducers'
// 引入全部并作为 reducers 对象
import * as reducers from './reducers';
// 数组
let [a, b, c] = [1, 2, 3];
a // 1
//对象
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
函数的参数也可以使用解构赋值。
function add ([x, y]) {
return x + y;
}
add([1, 2]); // 3
从作为函数实参的对象中提取数据
function userId({id}) {
return id;
}
function whois({displayName: displayName, fullName: {firstName: name}}){
console.log(displayName + " is " + name);
}
var user = {
id: 42,
displayName: "jdoe",
fullName: {
firstName: "John",
lastName: "Doe"
}
};
console.log("userId: " + userId(user)); // "userId: 42"
whois(user); // "jdoe is John"
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};
除了属性简写,方法也可以简写。
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
组装数组
const a = [1, 2];
const b = [...a, 3];
b // [1,2,3]
获取数组部分
const arr = ['a', 'b', 'c'];
const [first, ...rest] = arr;
rest; // ['b', 'c']
// With ignore
const [first, , ...rest] = arr;
rest; // ['c']
还可收集函数参数为数组。
function directions(first, ...rest) {
console.log(rest);
}
directions('a', 'b', 'c'); // ['b', 'c'];
代替 apply。
function foo(x, y, z) {}
const args = [1,2,3];
// 下面两句效果相同
foo.apply(null, args);
foo(...args);
组装对象
const a = { x : 1, y : 2 }
const b = { ...a, z : 3 }
b // {x:1, y: 2, z: 3}
Promise 用于更优雅地处理异步请求。比如发起异步请求:
fetch('/api/todos')
.then(res => res.json())
.then(data => ({ data }))
.catch(err => ({ err }));
定义 Promise 。
const delay = (timeout) => {
return new Promise(resolve => {
setTimeout(resolve, timeout);
});
};
delay(1000).then(() => {
console.log('executed');
});
这只是个简洁的ES6常用特性总结,更全和更详尽的文档请参阅Learn ES2015
React Router V4相对V2/V3几乎完全重写了,遵循 Just Component 的 API 设计理念。
react-router V4 分成了三个包:react-router-dom(for web)、react-router-native(for #native)、react-router(core)。在浏览器中,你只需使用react-router-dom。react-router-dom和react-router详见: remix-run/react-router#4648
在 React Router v3中,有 一个单独的 <Router>
组件. 它会提供一个 history
对象作为prop属性。
同时,可以使用routes
prop或者children
为<Router>
配置route
。
// v3
import routes from './routes'
<Router history={browserHistory} routes={routes} />
// or
<Router history={browserHistory}>
<Route path='/' component={App}>
// ...
</Route>
</Router>
而在V4中,一个重大变化就是提供了数种不同的router组件。每种都会创建一个history
对象。 <BrowserRouter>
创建 browser history
, <HashRouter>
创建 hash history
, and<MemoryRouter>
创建memory history
。
V4取消了集中的路由配置。
//v4
<BrowserRouter>
<div>
<Route path='/about' component={About} />
<Route path='/contact' component={Contact} />
</div>
</BrowserRouter>
这里要注意的是,router component 只能包裹一个元素。
// yes
<BrowserRouter>
<div>
<Route path='/about' component={About} />
<Route path='/contact' component={Contact} />
</div>
</BrowserRouter>
// no
<BrowserRouter>
<Route path='/about' component={About} />
<Route path='/contact' component={Contact} />
</BrowserRouter>
在V3中,<Route>
不是一个真正的组件,而是作为一个标签用来创建route配置对象。
/// in v3 the element
<Route path='contact' component={Contact} />
// 等同于
{
path: 'contact',
component: Contact
}
使用 v4,您可以像常规的 React 程序一样布局您应用中的组件,您要根据位置(特别是其 pathname )呈现内容的任何位置,您将呈现 <Route>
在 v4,<Route>
其实是一个组件,所以无论你在哪里渲染 <Route>
,它里面的内容都会被渲染。当 <Route>
的 path 与当前的路径匹配时,它将会渲染 component
, render
, or children
属性中的内容,当 <Route>
的 path
与当前的路径不匹配时,将会渲染 null
。
在 v3 中,<Route>
组件是作为其父 <Route>
组件的 children
嵌套其中的。
<Route path='parent' component={Parent}>
<Route path='child' component={Child} />
<Route path='other' component={Other} />
</Route>
当嵌套的 <Route>
匹配时,react 元素将会使用子 <Route>
和 父 <Route>
的 component
属性去构建,子元素将作为父元素的 children 属性。
<Parent {...routeProps}>
<Child {...routeProps} />
</Parent>
使用 v4,子 <Route>
会由父 <Route>
呈现。
<Route path='parent' component={Parent} />
const Parent = () => (
<div>
<Route path='child' component={Child} />
<Route path='other' component={Other} />
</div>
)
react-router v3 提供 onEnter
, onUpdate
, and onLeave
方法。这些方法本质上是重写(覆盖)了 react 生命周期方法。
使用 v4,你将会使用生命周期方法 通过 <Route>
渲染的组件,你可以使用 componentDidMount
或 componentWillMount
代替 onEnter
,你可以使用 componentDidUpdate
或者 componentWillUpdate
(更或者 componentWillReceiveProps
) 代替 onUpdate
,你可以使用 componentWillUnmount
代替 onLeave
。
在v3中,您可以指定一些子路由,并且只会渲染匹配到的第一个。
// v3
<Route path='/' component={App}>
<IndexRoute component={Home} />
<Route path='about' component={About} />
<Route path='contact' component={Contact} />
</Route>
v4 通过 <Switch>
组件提供了相似的功能,当 <Switch>
被渲染时,它仅会渲染与当前路径匹配的第一个子 <Route>
。
// v4
const App = () => (
<Switch>
<Route exact path='/' component={Home} />
<Route path='/about' component={About} />
<Route path='/contact' component={Contact} />
</Switch>
)
在V3, 如果你想从一个路径重定向到另一个, 例如从 /
到/welcome
, 你可以使用 <IndexRedirect >
.
// v3
<Route path="/" component={App}>
<IndexRedirect to="/welcome" />
</Route>
在V4,你可以使用<Redirect>
达到相同的效果。
// v4
<Route exact path="/" render={() => <Redirect to="/welcome" component={App} />} />
<Switch >
<Route exact path="/" component={App} />
<Route path="/login" component={Login} />
<Redirect path="*" to="/" />
</Switch>
在v3中,您可以使用与内部相同的匹配代码来检查路径是否匹配模式。在v4中,已被由path-to-regexp 库驱动的matchPath替代。
在v3中,您可以使用Pattern Utils.format Pattern从路径模式(可能在常量或**路由配置中)生成有效的路径以及包含名称参数的对象:
// v3
const THING_PATH = '/thing/:id';
<Link to={PatternUtils.formatPattern(THING_PATH, {id: 1})}>A thing</Link>
在V3中,您可以省略to
属性或将其设置为null
以创建没有href属性的锚标签。
// v3
<Link to={disabled ? null : `/item/${id}`} className="item">
// item content
</Link>
在v4中,您应该总是提供to
.如果你要设置to
,你可以做对其做一层封装。
// v4
import { Link } from 'react-router-dom'
const LinkWrapper = (props) => {
const Component = props.to ? Link : 'a'
return (
<Component {...props}>
{ props.children }
</Component>
)
)
<LinkWrapper to={disabled ? null : `/item/${id}`} className="item">
// item content
</LinkWrapper>
calc()
,url()
等$sidebar-width: 250px;
.main {
width: calc(100% - $sidebar-width);
}
//你会惊讶地发现,根本不work。没有报错,容器的大小却又不正确。如果你去审查你的dom元素,你会看到这个 — 被划掉了。因为,这是不合法的
//因为calc()是一个CSS函数,不是一个Sass函数。这就是说Sass会将整个表达式解释成一个字符串
$type-of-expression: type-of(calc(100% - $sidebar-width)); // string
.main{
width:calc(100% - #{$sidebar-width});
}
//$sidebar-width被认为是一个常规字符串,所以打出来就是它自己,当我们用插值这样做时,Sass编译这个样式文件时,它会用#{$sidebar-width}的值,即
.main {
width: calc(100% - 250px);
}
@for $i from 1 through $max {
.el:nth-of-type(#{$i}) {
...
}
}
//for循环和:nth-*()选择器一起使用。再一次说明,你需要使用插值变量,才能最终得到想得到的结果
@support
, @page
,@media
//如果你的变量在@media字符串后面,需要使用插值才可以
$value : screen;
@media #{$value} {
...
}
//如果@media后面紧跟(),你就不再需要插值变量里,因为sass会求出所有在这些括号里的值
$value: 1336px;
@media (max-width: $value) {
...
}
$media: screen;
$feature: -webkit-min-device-pixel-ratio;
$value: 1.5;
@media #{$media} {
.sidebar {
@media (#{$feature}: #{$value}) {
width: 500px;
}
}
}
//编译后
@media screen and (-webkit-min-device-pixel-ratio: 1.5) {
.sidebar {width: 500px; }
}
$value : cunstom;
selector-#{$value} {
property : value;
}
// d.js
export default function() {}
为何使用require
引用时需要取到解构default?而使用import d from './d'
是正确的?
const d = require('./d').default
// import d from './d'
来看下关键字default
到底是什么
// d.js
export default function() {}
// 等效于:
function a() {};
export {a as default};
Import
import a from './d';
// 等效于,或者说就是下面这种写法的简写,是同一个意思
import {default as a} from './d';
在此摘录汇总一些从社区看到的文章、面经中的比较有意思的JS题目~
Function.prototype.a = 'a';
Object.prototype.b = 'b';
function Person(){};
var p = new Person();
console.log('p.a: '+ p.a); // p.a: undefined
console.log('p.b: '+ p.b); // p.b: b
解释:实例p的原型链由Person、Object构成,没有Function
const person = {
name: 'menglinghua',
say: function (){
return function (){
console.log(this.name);
};
}
};
person.say()(); // ''(空字符串)
解释:this指向window,window.name===''
const person = {
name: 'menglinghua',
say: function (){
return () => {
console.log(this.name);
};
}
};
person.say()();
解释:箭头函数将this绑定到了声明域,指向person
setTimeout(() => console.log('a'), 0);
var p = new Promise((resolve) => {
console.log('b');
resolve();
});
p.then(() => console.log('c'));
p.then(() => console.log('d'));
console.log('e');
// 结果:b e c d a
解释:任务队列优先级:promise.Trick()>promise的回调>setTimeout>setImmediate
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
结果: 1 7 6 8 2 4 3 5 9 11 10 12
解释:原文地址,https://juejin.im/post/59e85eebf265da430d571f89#heading-4
Number('') // 0
parseInt('') // NaN
Number('123abc') // NaN
parseInt('123abc') // 123
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>axios取消请求</title>
</head>
<body>
<div>
<button class="btn">发送请求</button>
<button class="btn">取消请求</button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// 获取按钮
const buttons = document.querySelectorAll("button");
// 2.声明 cancelToken
let cancelToken = null;
// 发送请求
buttons[0].onclick = function () {
axios({
url: " http://localhost:3000/posts/1",
method: "get",
// 1.添加配置对象的属性
cancelToken: new axios.CancelToken(function (c) {
// 3.将 c 的值赋给 cancelToken
cancelToken = c;
}),
})
.then(function (response) {
console.log(response);
})
.catch(function (err) {
console.log("===>", err);
});
};
// 绑定第二个按钮事件取消请求
buttons[1].onclick = function name(params) {
if (typeof cancelToken === "function") {
cancelToken();
}
};
</script>
</html>
json-server --watch db.json -d 2000
{
"posts": [
{ "id": 1, "title": "json-server", "author": "typicode" }
],
"comments": [
{ "id": 1, "body": "some comment", "postId": 1 }
],
"profile": { "name": "typicode" }
}
sass-loader是 webpack的一个loader插件。这里说下options
参数includepaths
的作用。
我们在node-sass中可以看到includepaths
的说明。includepaths
的属性值是一个字符串数组,默认为[]
,接收路径(字符串)。解析引擎遍历这些传入的路径,查找sass模块,以尝试解析@import
声明。
这里我们以一个例子说明。
假定文档目录如下
.
├── a
│ ├── a.js
│ └──a.scss
├── b
│ └── b.scss
└── node_modules
└── bootstrap
└── scss
└── bootstrap.scss
// a.js
import './a.scss'
如果要在a.scss
中导入b.scss
和bootstrap.scss
应该如何导入?
inludepaths
不配置,也就是默认值[]
时。// webpack.config.js
module.exports = {
// ...
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "sass-loader",
options: {
// includePaths: []
}
}]
}]
}
}
// a.scss
@import '../b/b';
@import '~bootstrap/scss/bootstrap'
一般的sass模块使用相对路径导入,而在node_modules
中的sass模块只需要使用~
, 是因为:
webpack 提供一种解析文件的高级的机制。sass-loader 使用 node-sass 的 custom importer 特性,将所有的 query 传递给 webpack 的解析引擎(resolving engine)。只要它们前面加上 ~,告诉 webpack 它不是一个相对路径,这样就可以 import 导入 node_modules 目录里面的 sass 模块。
下面我们尝试配置includepaths
看看情况会如何。
includepath
//...
includepaths['./b', './node_modules/bootstrap/scss']
// a.scss
@import 'b';
@import 'bootstrap';
解析引擎会在路径'./b'和'./node_modules/bootstrap/scss'查找b.scss和bootstrap.scss,来解析这两个sass模块。
在JavaScript中,字符串的比较,是字符按从左到右一一对应比较的。
如:
"hello" > "hi"
先对首字母"h"进行比较。因为相等,所以再对下一位进行比较。而单个字符的比较,实际上是ASCII码的比较。字母"e"的ASCII码对应编号是101,而"i"的对应编号是105,所以 "e" > "i" 会返回false。对上面字符串的比较,可以拆分理解为:
"h" > "h" &&
"e" > "i" &&
"l" > "" &&
"l" > "" &&
"o" > ""
因此,
"hello" > "hi"; // return false
我们常常会需要比较两个日期的大小。比如2017-02-01
和 2017-03-20
。用字符串比较久很方便了。
'2017-03-01' > '2017-02-28' // true
'0' < '2017-09-06' // true
'9' > '2017-09-04' // true
'0' 和 ‘9’ 就可以作为最小和最大日期。
this
function F() {
this.a = 'a'
this.c = function () {
console.log(this)
}
}
var f = new F()
f.c()
f.c()
输出的this
指向构造器F的实例f, f是一个对象,即:{a: "a", c: ƒ()}
。那么下面几个示例就好理解了。
function F() {
this.a = 'a'
this.c = function () {
console.log(this.a)
}
}
var f = new F()
var c = f.c
f.c() // a
c() // undefined,this指向window
function F() {
this.a = 'a'
// 使用箭头函数绑定this
this.c = () => {
console.log(this.a)
}
}
var f = new F()
var c = f.c
f.c() // a
c() // a
现有内网虚机 10.88.88.88 部署前端静态资源,启用8000端口。同时http://abcd.com 根据/hyyy映射到虚机 http://10.88.88.88:8000。
先贴上nginx配置:
server {
listen 8000;
server_name localhost;
location /hyyy {
root /app/hyyy; #虚机用户目录
index index.html index.htm;
try_files $uri $uri/ /hyyy/index.html;
}
...
}
虚机文件目录
|-app
|- hyyy
|- hyyy
|- index.html
启动nginx, 测试结果:
10.88.88.88:8000/hyyy
自动跳转到了 10.88.88.88:8000/hyyy/
。虚机表现是我们所期望的。
但是wget
域名,结果发生错误
域名301重定向跳转后多出了个8000
端口。导致域名无法正常访问虚机。
如果server带了listen port , 自动跳转会带上这个port, 万一前面还有一层反向代理,就出现意外了。
明白了是啥坑,解决办法就是去掉带上带port
只要在配置中添加port_in_redirect off ;
配置如下:
server {
listen 8000;
server_name localhost;
port_in_redirect off ;
location /hyyy {
root /app/hyyy; #虚机用户目录
index index.html index.htm;
try_files $uri $uri/ /hyyy/index.html;
}
...
}
var destination = { a: 0 }
var src1 = { b: 1, c: 2 }
var src2 = { d: 3, e: 4 }
// ES5
Object.keys(src1).forEach(function (k) {
destination[k] = src1[k]
})
Object.keys(src2).forEach(function (k) {
destination[k] = src2[k]
})
// ES6
Object.assign(destination, src1, src2)
// Result
destination === { a: 0, b: 1, c: 2, d: 3, e: 4 }
var o1 = { a: 1, b: 1, c: 1}
var o2 = { b:2, c: 2}
var o3 = { c: 3 }
var obj = Object.assign({}, o1, o2, o3)
console.log(obj) // {a: 1, b: 2, c: 3}
var obj = { a: 0 }
var copy = Object.assign({}, obj)
console.log(copy) // {a: 0}
var array = ['a', 'b', 'c', 'd', 'e']
var arrayWithDups = ['a', 'b', 'c', 'd', 'e', 'e', 'e']
// ES5
array.filter(function (x) {
return x === 'e'
})[0]
// 'e'
// ES6
array.find(x => x === 'e') // 'e'
array.findIndex(x => x === 'e') //4
arrayWithDups.find(x => x === 'e') // 'e'
arrayWithDups.findIndex(x => x === 'e') //4
// ES5
var repeat = Array[3 + 1].join('foo ')
console.log(repeat) // 'foo foo foo'
// ES6
var repeat = 'foo '.repeat(3)
console.log(repeat) // 'foo foo foo'
// ES5
'hello'.indexOf('ello') === 1 // true
'hello'.indexOf('hell') === (4 - 'hell'.length) // true
'hello'.indexOf('ell') !== -1 // true
'hello'.indexOf('ell', 1) // false
'hello'.indexOf('ell', 2) !== -1 // false
// ES6
'hello'.startsWith('ello', 1) // true
'hello'.endsWith('hell', 4) // true
'hello'.includes('ell') // true
'hello'.includes('ell', 1) // true
'hello'.includes('ell', 2) // false
var isNaN = function (n) {
return n !== n
}
var isFinite = function (n) {
return (type0f v === 'number' && !isNaN(v) && v !== Inifity && v !== -Inifity)
}
isNaN(42) === false
isNaN(NaN) === true
isFinite(Inifity) === false
isFinite(-Inifity) === false
isFinite(NaN) === false
isFinite(123) === true
// ES6
Number.isNaN(42) === false
Number.isNaN(NaN) === true
Number.isFinite(Inifity) === false
Number.isFinite(-Inifity) === false
Number.isFinite(NaN) === false
Number.isFinite(123) === true
var isNaN = function (n) {
return n !== n
}
var isFinite = function (n) {
return (type0f v === 'number' && !isNaN(v) && v !== Inifity && v !== -Inifity)
}
isNaN(42) === false
isNaN(NaN) === true
isFinite(Inifity) === false
isFinite(-Inifity) === false
isFinite(NaN) === false
isFinite(123) === true
// ES6
Number.isNaN(42) === false
Number.isNaN(NaN) === true
Number.isFinite(Inifity) === false
Number.isFinite(-Inifity) === false
Number.isFinite(NaN) === false
Number.isFinite(123) === true
与 var 不同,let 和 const 声明的变量不会被提升到作用域顶部,而是位于临时死区TDZ。在声明前访问TDZ中变量就会报错。
if (condition) {
console.log(typeof value) // 引用错误
let value = 'blue'
}
而作用域外使用typeof就不会报错
console.log(typeof value) // "undefined"
if (condition) {
let value = 'blue'
}
在mobx中文文档-理解mobx对什么有反应中有上面一张图。
我们通过下面几行代码理解:
var o = { a: 1 }
// 对象o改变前
var o1 = o
o.a = 2
// 对象o改变后
var o2 = o
o2 === o1 // true
var o = { a: 1 }
// 对象o改变前
var o1 = o
o = { a: 2 }
// 对象o改变后
var o2 = o
o2 === o1 // false
var a = [1, 2]
var a1 = a
a[0] = 3
var a2 = a
a2 === a1 // true
var a = [1, 2]
var a1 = a
a = [3, 2]
var a2 = a
a2 === a1 // false
组件是否重绘就取决于props 或者 state 是否改变,以及shouldComponentUpdate
是否返回true
。这里讨论对象或者数组作为props 传入组件。只有对象或者数组真正的改变,才会触发重绘。
当我们搭配redux时,redux的不可变state解决了引用数据类型没有真正改变的问题。而当使用mobx时,就要注意何时数据真正的改变,何时组件重绘了。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.