GithubHelp home page GithubHelp logo

blog's People

Contributors

yuthelloworld avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

项目实践:从react-router v3迁移到v4

前言

今年3月初发布了react-router v4,相较之前的v3和v2版本做了一个破坏性的升级。遵循一切皆React Component的理念。静态路由变成了动态路由。这里记录下v3项目如何迁移到v4。
项目地址:https://github.com/YutHelloWorld/vortex-react

迁移步骤

  • 对React-Router和Redux同步进行重构
  • 重写路由
  • 代码分割
  • 琐碎的API替换

详细代码参阅这个PR


React-Router和Redux同步

这里我们仍然不使用react-router-redux这个库。为了和react-routerv4版本保持一致,react-router-redux发布了v5.0.0版本,你当然也可以使用它来实现这个功能。

1. 替换依赖包

v3我们引入的是react-router包,在v4我们只引入react-router-dom这个包。安装react-router-dom时会同时安装history

package.json

- "react-router": "^3.0.0",
+ "react-router-dom": "^4.1.2",

2. 改写对browserHistory的创建和当前location的获取

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

3. 对history绑定监听事件,把location的改变同步到Redux的store中

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))
}

一切似乎都很顺利,接着第一个坑来了

根据historyAPI提供的

// 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在创建的时候在内部已经引入了一个historyupdateLocation(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版本通过getComponetrequire.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
}
  1. 这个asyncComponent 函数接受一个importComponent 的参数,importComponent 调用时候将动态引入给定的组件。
  2. componentDidMount 我们只是简单地调用importComponent 函数,并将动态加载的组件保存在状态中。
  3. 最后,如果完成渲染,我们有条件地提供组件。在这里我们如果不写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组件。


琐碎API的替换

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 🤣 。

参考

捋一捋Date的一些小技巧

假定当前日期:2017/8/15

0. 年份从1900开始计

var today = new Date()
console.log(today.getYear()+1900)
// 2017

1. 月份是从0开始计的

var today = new Date()
console.log(today.getMonth())
// 7

2. 获得当月最后一天

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就是上月的最后一天。

3. 二月31到底是几月几号?

var aDate = new Date(2017, 1, 31)
console.log(aDate)
// 输出:Fri Mar 03 2017 00:00:00 GMT+0800 (CST)

date会自动往后推算

4. 日期格式化

业务上常常会需要把Date转换成自定义的日期字符串。

这里介绍个日期格式化的工具: taylorhakes/fecha/

fecha.format(new Date(), 'YYYY年MM月DD日')
// 2017年09月06日

[Visual Studio Code] React/ES7扩展

最近转投微软爸爸怀抱,使用VS Code。由于是做React相关的前端开发。这里记录下装的一些有用的扩展。参考了: sundaypig/blog#2


vs-1.jpg
vs-2.jpg

React知识地图--Redux

Redux

redux

-- React Redux 数据流

通过这张流程图,我们可以更好的理解Redux和React直接数据如何流通,关系如何映射。

让我们一步步来了解图中的各个概念。

action & actionCreator

action creator 就是函数而已,负责构建一个 action (是的,action creator 这个名字已经很明显了)并返回它。通过几行简单的代码就可以解释清楚了!

const actionCreator = function () {
  return {
    type : 'AN_ACTION'
  }
}

一般约定 action 是一个拥有 type 属性的对象。

console.log(actionCreator())
//  { type: 'AN_ACTION' }

reducer

Reducer 函数只是一个纯函数,它接收应用程序的当前状态以及发生的 action,然后返回修改后的新状态(或者有人称之为归并后的状态)。Reducer 函数是 action 的订阅者。

const reducer = function (state = {}, action) {
  console.log('reducer was called with state', state, 'and action', action);

  return state;
}

Store

以上,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' }

combineReducers

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

/**
 * 解析一个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' 
}

nginx配置

#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

nvm使用新版淘宝镜像

linux/mac设置新版淘宝镜像

添加一个环境变量即可

vim ~/.bashrc

然后最后新增一行:

export NVM_NODEJS_ORG_MIRROR=https://cdn.npmmirror.com/binaries/node

最后保存 :wq使之生效

source ~/.bashrc

windows设置

修改setting文件

node_mirror: https://npmmirror.com/mirrors/node/
npm_mirror: https://npmmirror.com/mirrors/npm/

WebStorm“奇技淫巧”

本文介绍一些日常使用WebStorm的设置和快捷键。欢迎补充,互惠你我。🙃

WebStorm

版本: WebStorm 2017.2
操作系统:Mac OS

懒人一键配置主题、插件、CodeStyle

直接克隆这个仓库导入使用:https://github.com/YutHelloWorld/WebStrom-Setting

常用设置

设置Terminal字体

  • Preferences > Editsor > Colors & Fonts > Console Font

禁止实时自动保存

  1. Preferences > Apearance & Behavior > System Settings
  2. 取消勾选 "Save files on frame deactivation"和"Save files automa

tically if application is idle for"

文件未保存时显示星星(*)标记

  1. Preferences > Editor > General > Editor Tabs
  2. 勾选 “Mark modified tabs with asterisk”

设置鼠标双击选中驼峰式命名的变量

  1. Preferences > Editor > General
  2. 取消勾选 Honor "CameHumps" words settings when selecting on double click
  3. Preferences > Editor > General > Smart Keys
  4. 勾选 Use "CameHumps" words

启用Eslint校验JS代码

首先确保安装了Nodejs, 并且工程中配置了Eslint相关文件:.eslintrc.eslinignore

  1. Preferences > Languages & Frameworks > Javascript > Code Quality Tools > Eslint
  2. 勾选Eable
  3. 输入Node和Eslint路径
  4. 选中Automatic search 并Apply
  5. Editor > Inspections
  6. 取消勾选Javascript下的所有子选项
  7. 勾选Javascript > Code quality tools > 第一个Eslint
  8. Apply

Code Style

Preferences > Code Style
分别设置各个类型文件的代码风格。

支持JSX语法

  1. Preferences > Languages & Frameworks > Javascript
  2. 选择React JSX

快捷键

这里快捷键选用的是Mac OS

格式化

command + shift + L

快速定位变量、函数、模块声明源

command + 鼠标左键

多行光标

option + 鼠标左键

收藏代码某一行

收藏快捷键:将光标移动到目标行,按快捷键 F3
跳转至收藏的行: command+F3

高级
给收藏处标序号:option + F3

跳转到某行

command + L

打开某个文件

command + shift + o

查找和替换

  • 查找:command + f
  • 替换:command + r

全局查找和替换

  • 查找:command + shift + f
  • 替换:command + shift + r

以上只是很小的一些设置技巧,欢迎补充😆

一些常见的CSS面试问题

display有哪些值?说明他们的作用。

block       	块类型。默认宽度为父元素宽度,可设置宽高,换行显示。
none        	缺省值。象行内元素类型一样显示。
inline      	行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。
inline-block    默认宽度为内容宽度,可以设置宽高,同行显示。
list-item   	像块类型元素一样显示,并添加样式列表标记。
table       	此元素会作为块级表格来显示。
inherit     	规定应该从父元素继承 display 属性的值。

介绍一下标准的CSS的盒子模型?低版本IE的盒子模型有什么不同的?

(1)有两种, IE 盒子模型、W3C 盒子模型;
(2)盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border);
(3)区  别: IE 的content部分把 border 和 padding计算了进去;

如何居中div?

水平居中:给div设置一个宽度,然后添加margin:0 auto属性

div{
  width: 200px;
  margin: 0 auto;
}

让绝对定位的div居中

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;}

[译]React Component最佳实践

原文:Our Best Practices for Writing React Components .
这里意译。有些点在之前的文章里提到过:#2

Class写法

如果组件带有state或者方法,就使用Class写法。

1. 引入CSS

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

2. 初始化State

使用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 }

3. 初始化propTypesdefaultProps

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'
  }

propTypesdefaultProps的声明应该置顶便于其他开发者阅读。在React v15.3.0版本,推荐使用prop-types这个包替代React.PropTypes。
重要的一点:所有的组件都应当有propTypes验证。

4. 组件内的方法

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)

5. this.setState()是异步的。应该使用函数入参

this.setState(prevState => ({ expanded: !prevState.expanded }))

6. 一个组件或者元素含有多个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>
    )
  }

7. 避免在子组件中使用闭包

 <input
           type="text"
            value={model.name}
            // onChange={(e) => { model.name = e.target.value }}
            // ^ Not this. Use the below:
            onChange={this.handleChange}
            placeholder="Your Name"/>

8.完整的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>
    )
  }
}

函数式组件写法

1. propTypes

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

2. Destructuring Props and defaultProps

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>
  )
}

3. 避免箭头函数写法

const ExpandableForm = ({ onExpand, expanded, children }) => {

虽然语法没问题,但是这里函数是匿名函数。

4. 高阶组件

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)

5. 完整代码

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 重置

原文:https://www.joshwcomeau.com/css/custom-css-reset/

每当我开始一个新项目时,首要任务就是打磨 CSS 语言中的一些粗糙边缘。我使用一组功能性的自定义基线样式来做到这一点。

很长一段时间,我使用 Eric Meyer 著名的CSS Reset。这是 一大块CSS代码,但它有点长;它已经十多年没有更新了,从那以后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 重置”的经典定义,但保证了创造性和自由性。

1. 盒子尺寸模型

未完。。

聊聊箭头函数的this到底指向啥

先放结论:

箭头函数体内的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始终指向的是实例。

[译]React Strict Mode

原文:https://reactjs.org/docs/strict-mode.html

严格模式

StrictMode是一个用以标记出应用中潜在问题的工具。就像FragmentStrictMode不会渲染任何真实的UI。它为其后代元素触发额外的检查和警告。

注意:
严格模式检查只在开发模式下运行,不会与生产模式冲突。

你可以在应用的任何地方启用严格模式。例如:

import React from 'react';

function ExampleApplication() {
  return (
    <div>
      <Header />
      <React.StrictMode>
        <div>
          <ComponentOne />
          <ComponentTwo />
        </div>
      </React.StrictMode>
      <Footer />
    </div>
  );
}

在上面的例子中,不会对组件HeaderFooter进行strict mode检查。然而ComponentOneComponentTwo以及它们所有的后代将被检查。

StrictMode目前有助于:

识别具有不安全生命周期的组件

如同在博客中阐明的,在异步React应用中使用某些老式的生命周期方法不安全。但是, 如果应用程序使用第三方库, 则很难确保不使用这些生命周期方法。幸运的是, 严格的模式可以帮助解决这个问题!

当启用严格模式, React将编译一个所有使用不安全生命周期组件的列表,并打印一条关于这些组件的警告信息,就像:

有关旧式字符串ref API用法的警告

以前,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。它们稍微灵活一些, 因此它们将保持为高级功能。

学习更多有关createRef API内容

检测意外的副作用

理论上,React在两个阶段起作用:

  • 渲染阶段决定了需要对 DOM 进行哪些更改。在此阶段, React调用render(方法), 然后将结果与上一次渲染进行比较。
  • 提交阶段是React执行任何更改的阶段。(在React DOM中, 指React插入、更新和删除 dom 节点)。在此阶段React也调用生命周期, 如 componentDidMountcomponentDidUpdate

提交阶段通常很快,但是渲染可能很慢。因此, 即将出现的异步模式 (默认情况下尚未启用) 将呈现工作分解为片断, 暂停和恢复工作以避免阻止浏览器。这意味着在提交之前, 反应可能不止一次地调用渲染阶段生命周期, 或者它可以在不提交的情况下调用它们 (因为错误或更高的优先级中断)。

渲染阶段的生命周期包括以下class component方法:

  • constructor
  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState 更新函数 (第一个形参)

因为以上方法可能不止一次被调用,所以它们中不包含副作用尤为重要。忽略此规则可能会导致各种问题, 包括内存泄漏和无效的应用程序状态。不幸的是, 很难发现这些问题, 因为它们通常都是不确定的

严格模式不能自动检测到你的副作用, 但它可以帮助你发现它们, 使其更具确定性。这是通过有意地双调用以下方法来完成的:

  • Class component constructor
  • render
  • setState 更新函数 (第一个形参)
  • static getDerivedStateFromProps

注意:
只在开发模式生效。生产模式下不会生命周期不会被双调用。

举个例子,考虑以下代码:

class TopLevelRoute extends React.Component {
  constructor(props) {
    super(props);

    SharedApplicationState.recordEvent('ExampleComponent');
  }
}

乍一看, 这段代码似乎没有问题。但是如果 SharedApplicationState.recordEvent 不是幂等, 那么多次实例化此组件可能会导致无效的应用程序状态。这种微妙的 bug 可能不会在开发过程中显现出来, 或者它可能会不一致, 因此被忽略。

通过有意的双调用方法 (如组件构造函数), 严格模式使得这样的行为更容易被发现。

React知识地图--React

React

JSX

const element = <h1>Hello, world!</h1>;

在 JSX 中使用表达式

你可以任意地在 JSX 当中使用 JavaScript 表达式,在 JSX 当中的表达式要包含在大括号里。

<h1>{title}</h1>

JSX 嵌套

如果 JSX 标签是闭合式的,那么你需要在结尾处用 />, 就好像 XML/HTML 一样:

const element = <img src={user.avatarUrl} />;

互相嵌套

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

className

class 是保留词,所以添加样式时,需用 className 代替 class

<h1 className="loading">Hello world</h1>

Mapping Arrays to JSX

<div>
  {text.map(item => (
    <p key={item.id}>{item.text}</p>
  ))}
</div>

React组件3种形式

分别是 React.createClass, class 和 Stateless Functional Component(无状态组件)。推荐尽量使用最后一种,保持简洁和无状态。这是函数,不是 Object,没有 this 作用域,是 pure function。

createClass

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版本会被移除。

stateless function

const MyComponent = () => (
  <div>
    ES6
  </div>
);

class

class MyComponent extends React.Component {
  render() {
    return (
      <div>ES6</div>
    );
  }
}

PropTypes

使用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,
  }
}

State

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...
}

destructuring & spread attributes

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知识地图--ES6

ES6


这里梳理下React技术栈需要的最小知识集,让你可以最短时间掌握React,Redux,React-Router,ES6的相关知识,更快的上手React”全家桶“。预计会有ES6、React、Redux、React-Router、Webpack,实时更新目录。

变量声明

let 和 const

不要用var,而是用letconstconst声明一个只读的常量,let用来声明变量,constlet 都是块级作用域。

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

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';

ES6 对象和数组

解构赋值

// 数组
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

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:从V2/V3迁移到V4

官方文档:Migrating from v2/v3 to v4

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

The Router

在 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>

Routes

在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>
)

on* properties

react-router v3 提供 onEnter, onUpdate, and onLeave 方法。这些方法本质上是重写(覆盖)了 react 生命周期方法。

使用 v4,你将会使用生命周期方法 通过 <Route> 渲染的组件,你可以使用 componentDidMountcomponentWillMount 代替 onEnter,你可以使用 componentDidUpdate 或者 componentWillUpdate (更或者 componentWillReceiveProps) 代替 onUpdate,你可以使用 componentWillUnmount 代替 onLeave

<Switch>

在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>
)

<Redirect>

在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>

PatternUtils

matchPattern(pattern, pathname)

在v3中,您可以使用与内部相同的匹配代码来检查路径是否匹配模式。在v4中,已被由path-to-regexp 库驱动的matchPath替代。

formatPattern(pattern, params)

在v3中,您可以使用Pattern Utils.format Pattern从路径模式(可能在常量或**路由配置中)生成有效的路径以及包含名称参数的对象:

// v3
const THING_PATH = '/thing/:id';

<Link to={PatternUtils.formatPattern(THING_PATH, {id: 1})}>A thing</Link>

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>

sass 插值:#{} 应用

  1. css函数-css函数中的变量,如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-*()选择器一起使用。再一次说明,你需要使用插值变量,才能最终得到想得到的结果
  1. css指令-如 @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; } 
}
  1. 选择器-使用变量作为一个选择器,或者选择器的一部分
$value : cunstom;
selector-#{$value} {
    property : value;

Export default到底是什么含义?

问题

// d.js
export default function() {}

为何使用require引用时需要取到解构default?而使用import d from './d'是正确的?

const  d = require('./d').default
// import d from './d'

来看下关键字default到底是什么

default关键字

// d.js
export default function() {}

// 等效于:
function a() {};
export {a as default};

Import

import a from './d';

// 等效于,或者说就是下面这种写法的简写,是同一个意思
import {default as a} from './d';

参考: http://imweb.io/topic/582293894067ce9726778be9

一些有意思的JS题目

在此摘录汇总一些从社区看到的文章、面经中的比较有意思的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

this

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

axios取消请求

<!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选项includepaths详解

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.scssbootstrap.scss应该如何导入?

  1. 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看看情况会如何。

  1. 配置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-012017-03-20。用字符串比较久很方便了。

'2017-03-01' > '2017-02-28'    // true
'0' < '2017-09-06'    // true
'9' > '2017-09-04'    // true

'0' 和 ‘9’ 就可以作为最小和最大日期。

几行代码理解this

  1. 构造函数和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

[nginx]域名映射虚机,带端口自动跳转解决

案例

现有内网虚机 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;
    }
...
}

ES6新增的内置方法一览

对象

对象合并

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

[阅读]最近在读《深入理解ES6》,开个帖子写点笔记

块级做用域绑定

1. 块级作用域存在于

  • 函数内部
  • 块中(字符{和}之间的区域)

2. 临时死区(Temporal Dead Zone)

与 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'
}

3. 默认使用const,只在确实有需要改变变量的值时使用let

几行代码理解:传入react组件的对象或者数组作为props,何时触发组件重绘

qq20171130-103536 2x
mobx中文文档-理解mobx对什么有反应中有上面一张图。

我们通过下面几行代码理解:

  1. 对象
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
  1. 数组
 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时,就要注意何时数据真正的改变,何时组件重绘了。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.