eslint

  1. 本地安装 eslint
    npm install eslint --save-dev
    npm install –save-dev babel-eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y
  2. 创建基础的 .eslintrc.js
    eslint --fix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//.eslintrc.js
module.exports = {
// 环境
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true
},
// 使用的扩展库
"extends": ["airbnb"],
// 解析器用于解析代码
parser: "babel-eslint",
// 解析器配置
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2018,
"sourceType": "module"
},
// 第三方插件
"plugins": [
"react"
],
// 规则
"rules": {
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/react-in-jsx-scope": 2,
"indent": [
"error",
"tab"
],
"linebreak-style": [
"error",
"windows"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"react/jsx-indent": [ // 解决react里面的缩进问题
"error",
"tab"
],
"react/jsx-indent-props": [ //
"error",
"tab"
],
"no-tabs": "off", // 禁止缩进错误
// 允许使用 for in
"no-restricted-syntax": 0,
"guard-for-in": 0,
// 允许在 .js 和 .jsx 文件中使用 jsx
"react/jsx-filename-extension": [1, {
"extensions": [".js", ".jsx"]
}],
// 不区分是否是 无状态组件
"react/prefer-stateless-function": 0
}
};
  1. package.json
1
2
"lint": "eslint --ext .js --ext .jsx src",
"lint-fix": "eslint --fix --ext .js --fix --ext .jsx src",
  1. pre-commit
    如果项目使用了 git,可以通过使用 pre-commit 钩子在每次提交前检测,如果检测失败则禁止提交。可以在很大一定程度上保证代码质量。npm install pre-commit –save-dev`
1
2
3
4
//package.json
"pre-commit": [
"lint"
]

属性校验和默认属性

1
2
3
4
5
6
7
8
9
10
PostItem.propTypes = {
post: PropTypes.shape({
id: PropTypes.number,
title: PropTypes.string,
author: PropTypes.string,
date: PropTypes.string,
vote: PropTypes.number,
}).isRequired,//必须传入的属性
onVote: PropTypes.func.isRequired,
};

事件处理

  1. 在constructor中绑定this.handleClick=this.handleClick.bind(this)
  2. 可传入参数onClick={this.handleClick.bin(this, item)}
  3. 使用ES7属性初始化语法onClick={this.handleClick}
    1
    2
    3
    4
    5
    6
    handleClick = (event) => {
    const number = ++this.state.number;
    this.setState({
    number:number
    })
    }

表单

  1. 文本框
    1
    2
    3
    4
    5
    handleChange = (event) => {
    const target = event.target;
    this.setState({[target.name]: target.value})
    }
    <input type="text" name="name" value={this.state.name} onChange={this.handleChange}>

ref非受控组件

  1. 在Dom元素上使用ref,回调会接收当前DOM元素作为参数
    <input defaultValue="something" type="text" ref={(input) => this.input = input} />
  2. 在组件上使用ref,回调会接收当前组件的实例(类组件可以,函数组件不行)
  3. 父组件要获取子组件的DOM元素而不是实例
    1
    2
    3
    4
    5
    6
    //Parent 使用自定义属性inputRef
    <Children
    inputRef={el => this.inputElement = el}
    />
    //子组件使用父子间传递的inputRef,为input的ref赋值
    <input ref={props.inputRef} />

state修改

注意,不要使用push,pop,shift,unshift,splice等方法,因为这些方法会修改原数组,而concat,slice,filter会返回一个新的数组。

数组

  1. 数组类型状态books,新增一本书
    1
    2
    3
    4
    5
    6
    7
    8
    //方法一 concat 创建新数组
    this.setState(preState => {
    books: preState.books.concat(['React Guide']);
    })
    //方法二 ES6 spread syntax
    this.setState(preState => {
    books: [...preState.books, 'React Guide'];
    })
  2. 从books中截取部分元素作为新状态时,可以使用数组的slice方法:
    1
    2
    3
    this.setState(preState => ({
    books: preState.books.slice(1,3);
    }))
  3. 从books中过滤部分元素后,作为新状态时,可以使用数组的filter方法:
    1
    2
    3
    4
    5
    this.setState(preState => ({
    books: preState.books.filter(item => {
    return item !=='React';
    });
    }))

    状态类型是普通对象

  4. 使用es6 Object.assgin方法:
    1
    2
    3
    this.setState(preState => ({
    owner: Object.assign({}, preState.owner, {name: 'Jason'});
    }))
  5. 使用对象扩展语法
    1
    2
    3
    this.setState(preState => ({
    owner: {...preState.owner, name: 'Jason'};
    }))

    组件通信

  6. 组件挂载通信
    componentDidMount
  7. 组件更新阶段通信
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    componentWillReceiveProps(nextProps) {
    var that = this;
    if(nextProps.category !== this.props.category) {
    fetch('...').then(function(response) {
    response.json().then(function(data) {
    that.setState({user: data})
    })
    })
    }
    }
  8. context 多层级自动传递
    让任意层级的子组件都可以获取父组件中的状态和方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //父 UserListContainer
    getChildContext() {
    return {onAddUser: this.handleAddUser};
    }
    UserListContainer.childContextTypes = {
    onAddUser: PropTypes.func
    }
    //子 UserAdd
    UserAdd.contextTypes {
    onAddUser: PropTypes.func
    }
    使用this.context.onAddUser(this.state.newUser)

性能优化

避免不必要的组件渲染

  1. shouldComponentUpdate
    1
    2
    3
    4
    5
    6
    shouldComponentUpdate(nextProps, nextState) {
    if(nextProps.item === this.props.item) {
    return false;
    }
    return true;
    }
  2. class xxx extends React.PureComponent 代替手写shouldComponentUpdate

    使用key

高阶组件

const EnhancedComponent = higherOrderComponent(WrappedComponent);

主要分为以下四种情况:

  1. 操纵props,进行拦截props并进行修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    funciton withPersistentData(WrappedComponent) {
    return class extends Component {
    componentWillMount() {
    let data = localStorage.getItem("data");
    this.setState({data});
    }
    render() {
    //{...this.props}把传递给当前组件的属性继续传递给包装的组件
    return <WrappedComponent data={this.state.data} {...this.props} />
    }
    }
    }
  2. 通过ref访问组件实例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function withRef(wrappedComponent) {
    return class extends React.Component {
    constructor(props) {
    super(props);
    this.someMethod = this.someMethod.bind(this);
    }
    }
    someMethod() {
    this.wrappedInstance.someMethodInWrappedComponent();
    }
    render() {
    //为包装组件添加ref属性,从而获取该组件实例并赋值给this.wrappedInstance
    return <WrappedComponent ref={(instance) => {this.wrappedInstance = instance}} {...this.props}>
    }
    }
  3. 组件状态提升
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function withControlledState(WrappedComponent) {
    return class extends React.Component {
    constructor(props) {
    super(props);
    this.state = {
    value: ''
    };
    this.handleValueChange = this.handleValueChange.bind(this);
    }
    handleValueChange(event) {
    this.setState({
    value: event.target.value
    });
    }
    render() {
    //newProps保存受控组件需要使用的属性和事件处理函数
    const newProps = {
    controlledProps: {
    value: this.state.value,
    onChange: this.handleValueChange
    }
    };
    return <WrappedComponent {...this.props}{...newProps}>
    }
    }
    }
  4. 用其他元素包装组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function withRedBackground(WrappedComponent) {
    return class extends React.Component {
    render() {
    return (
    <div style={{backgroundColor: 'red'}}>
    <WrappedComponent {...this.props}/>
    </div>
    )
    }
    }
    }

    参数传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import React, {Component} from 'react'
    function withPersistentData(WrappedComponent, key) {
    return class extends Component {
    componentWillMount() {
    let data = localStorage.getItem(key);
    this.setState({data});
    }
    render() {
    return <WrappedComponent data={this.state.data} {...this.props} />
    }
    }
    }
    const MyComponentWithPersistentData = withPersistentData(MyComponent, 'data');
    //新版本的参数传递为HOC(...params)(WrappedComponent)
    //可改写为
    function withPersistentData = (key) => (WrappedComponent) => {
    ......
    }
    const MyComponentWithPersistentData = withPersistentData('data')(MyComponent)

react-router

npm install react-router-dom

Redux

  1. 唯一数据源
  2. 保持应用状态只读,但需要修改应用状态时,必须发送一个action
  3. 引用状态的改变通过纯函数完成,actio表明修改应用状态的意图,真正对应用状态做修改的是reducer

    action

    ation是Redux中信息的载体,是store唯一的信息来源。把action发送给store必须通过store的dispatch方法。action是普通的JavaScrpit对象,但每个action必须有一个type属性描述action的类型,type一般被定义为字符串常量。除type外,action的结构完全由自己决定,但应该确保action的结构能清晰地描述实际业务场景。一般通过action creator创建action, action creator是返回action的函数。

    reducer

    reducer根据action做出响应,决定如何修改应用的状态state。reducer是一个纯函数,接受两个参数,当前的state和action。但应用变得复杂,这个reducer可以拆分出多个reducer,每个reducer处理state中的部分状态。redux还提供了combineReducers函数,用于合并多个reducer。

    store

    store是action和reducer之间的桥梁,主要负责以下几个工作:
  4. 保存应用状态
  5. 通过方法getState()访问应用状态
  6. 通过方法dispatch(action)发送更新状态的意图
  7. 通过方法subscribe(listener)注册监听函数、监听应用状态的改变
    一个Redux应用只有一个store,store保存了唯一数据源。store通过createStore()函数创建,创建时需要传递reducer作为参数。

性能优化

React Router和Redux一起使用会造成重复渲染,可以使用高阶组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function connectRoute(WrappedComponent) {
return class extends React.Component {
shouldComponentUpdate(nextProps) {
return nextProps.location !== this.props.location;
}

render() {
return <WrappedComponent {...this.props} />;
}
}
}

//connectRoute使用包裹Home,Login
const AsyncHome = connectRoute(asyncComponent(() => import("../Home")));
const AsyncHome = connectRoute(asyncComponent(() => import("../Login")));

Immutable.JS

  1. 保证数据的不可变
  2. 丰富的API,如Map,List,Set,Record
  3. 优化的性能
    npm install immutable
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    (1)使用Immuta.JS创建模块使用的初始state:
    import Immutable from "immutable";
    const initialState = Immutable.fromJS({
    allIds: [],
    byId: {}
    });
    posts模块中的每一个reducer设置state默认值的方式也需要修改:
    const allIds = (state = initialState.get("allIds), action) => {
    ...
    }
    const byId = (state = initialState.get("byId"), action) => {
    ...
    }
    (2)当reducer接收到action时,reducer内部也需要通过Immutable.JS的API来修改state,代码如下
    const allIds = (state = initialState.get("allIds"), action) => {
    switch (action.type) {
    case types.FETCH_ALL_POSTS:
    // Immutable.List创建一个List类型的不可变对象
    return Immutable.List(action.postIds);
    case types.CREATE_POST:
    // 使用unshift向List类型的state增加元素
    return state.unshift(action.post.id);
    default:
    return state;
    }
    };
    const byId = (state = initialState.get("byId"), action) => {
    switch (action.type) {
    case types.FETCH_ALL_POSTS:
    // 使用merge合并获取到的post列表数据
    return state.merge(action.posts);
    case types.FETCH_POST:
    case types.CREATE_POST:
    case types.UPDATE_POST:
    // 使用merge修改对应post的数据
    return state.merge({ [action.post.id]: action.post});
    default:
    return state;
    }
    }
    引入npm install redux-immutable

Mobx

state

computed value

1
2
3
4
5
6
7
8
// computed 一般用于接收一个函数
const isYoung = computed(() => {
return person.age < 25;
})
// @computed一般用于修饰class的属性的getter方法
@computed get isYoung() {
return this.age < 25;
}

reaction

autorun

autorun会返回一个清楚函数disposer,但不再需要观察相关state变化时,可以调用disposer函数清楚副作用

1
2
3
4
5
6
var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reducer((a, b) => a + b, 0));
var disposer = autorun(() => console.log(sum.get()));//6
numbers.push(4);//10
disposer();// 清除autorun
numbers.push(5)// 没有输出

reaction

rection(() => data, data => { sideEffect }, options?)
第一个函数返回被观测的state,这个返回值同时时第二个函数的输入值,只有第一个函数的返回值发生变化时,第二个函数才会被执行。

when

when(() => condition, () => { sideEffect })
condition会自动响应它使用的任何state的变化,但condition返回true时,函数sideEffect会执行,且执行一次。

action

action是用来修改state的函数,Mobx提供了API来包装action函数

action(fn)

@action classMethod

react中使用Mobx

API
observer/@observer
Provider 利用React的context机制把引用所需的state传递给子组件
injext 是一个高阶组件,和Provier结合使用,用于从Provider提供的state中选取所需数据,作为props传递给目标组件。
inject("store1","store2")(observer(MyComponent))

@inject("store1","store2") @observer MyComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { observer, inject, Provider } from "mobx-react";
import { observable } frm "mobx";'

@observer
@inject("store")// inject从context中取出store对象,注入到组件的Props中
class App extends Component {
render() {
const { store } = this.props;
return (
<div>
<ul>
{store.map(todo => <TodoView todo={todo} key={todo.id} />)}
</ul>
</div>
)
}
}

const TodoView = observer(({ todo }) => {
return <li>{todo.title}</li>;
})

//