2个月的React尝鲜总结

身为后端,一上来就魔改了一个6万行的开源前端项目,做一下总结,梳理一下用到的知识点。

一、JS ES6语法糖

初识JS,对ES6的语法糖还是非常喜欢的
下面介绍一下项目中用到的一些语法糖

模板字符串

const name = 'shn';
console.log(`I'm ${name}`);

Object/Array unpack

Object unpack:

const { payload, viewport } = this.props;
function testfunc({ payload, viewport }){
    //do something
}


const testvar = {
    ...payload,
    ...viewport,
    name: 1, //可对上面unpack进行值覆盖
}

Array unpack:

const arr1 = [1,2,3];
const arr2 = [2,3,4];

const arr = [
    ...arr1,
    ...arr2,
]

for对Array值遍历

for(let e in [1,2,3,4]){
    console.log(e);
}

匿名函数表达式

简单表达式:

[1,2,3].map(e => e+1)

[1,2,3].map(e => ({value: e}))

复杂表达式:

[1,2,3].map(e => {
    let v = e + 1;
    return v;
})

函数bind

函数都有一个this变量,这个一般是由父级函数指定的,缺省是集成父级函数的this
class,我们一般是希望他是类实例,但是如果作为回调函数时,由于无法保证父级函数的this就是本类实例,所以需要手动指定

this.onChange = this.onChange.bind(this);

作用2:用于参数绑定
比如在一组组件中,需要根据组件的index来决定值具体怎么处理

onInputChange(index, value){
    this.setState((prevState) => {
        let inputs = prevState.inputs;
        inputs[index] = value;
        return {inputs}
    });
}

render(){
    return (
        <div>
        {[1,2,3,4].map((e, i) => {
            <InputControl onClick={this.onInputChange.bind(this, i)} />
        })}
        </div>
    )
}

二、React

生命周期

Mount过程

Update过程

用来处理props和state被修改的过程

Unmount过程

这里只有componentWillUnmount(),应该很少用到。
我涉及的项目中就没用到。

constructor

主要用于组件实例初始化,只会运行一次。
通常是用来初始化state
如:

constructor(props) {
    super(props);
    this.state = {
      value: props.value || '';
    };
}

render

用于组件的渲染。有点像网页模板。
这里return只能返回单个对象,如果要返回多个,那么就套一层div

render(){
    return(
        <div>
            <Button name={this.state.names[0]}>{this.state.names[0]}</Button>
            <Button name={this.state.names[1]}>{this.state.names[1]}</Button>
        </div>
    )
}

参数传递

render(){
    return (
        <Button
            type='primary' //常量参数
            name={this.state.buttonName} //使用变量参数
            onChange={this.onButtonChange.bind(this)}
            disabled  //指定则表示disabled=true
        />
    )
}

state

主要用于render中的变量绑定,当state变化时,会重新调用render()方法

变量使用

this.state.valueOfSomeCompent;

变量修改

  1. 常规修改
let value = this.state.value;
value = value + 1;
this.setState({value});
  1. 回调修改
this.setState((prevState, props) => {
    return {value: prevState.value + 1};
})

两者区别:
方式1为非原子性操作,方式2为原子性操作

props

保存了组件初始化时输入的参数,只读。
部分变量用作state的默认值。
通常由父组件的state生成子组件的props,所以当父组件的state发生变化的时候,会触发componentWillReceiveProps(),但是不会再重新触发constructor()。

组件key

性能相关:当一列表中的某组件发生变化时,可以快速定位并进行dom树更新。
key是react的一个保留参数名,可用于任意组件中。

用法:

render(){
    let _this = this;
    return (
        <div>
        {[1,2,3,4].map((e, i) => {
            <InputControl
                key={`input-${i}`}
                onClick={this.onInputChange.bind(_this, i)}  //注意:这里的this需要使用之前保存过的引用
            />
        })}
        </div>
    )
}

onChange概念

这是一个非常重要的概念,函数名非必须为onChange
当父组件需要得到子组件的值的时候,需要利用他进行值传递。
所以当组件嵌套的时候,这也是层层嵌套的。

当然有时候没必要那么层层嵌套,使用dispatch(reducer)就行了

Component/Container/App概念

App为一个页面实例
一个App则由诺干Container构成
一个Container则是由诺干个Component构成
其中的Component可作为组件复用
所以设计Component需要尽可能抽象

PropTypes/defaultProps

PropTypes用来定义和约束组件输入参数

const propTypes = {
  value: PropTypes.bool, //可选参数
  label: PropTypes.string.isRequired, //必选参数
  onChange: PropTypes.func,
};

defaultProps用来设置参数的默认值

const defaultProps = {
  value: false,
  onChange: () => {},
};

绑定到组件


export default class CheckboxControl extends React.Component { } CheckboxControl.propTypes = propTypes; CheckboxControl.defaultProps = defaultProps;

三、Redux

store

来以存放应用中所有的state,应用中应有且仅有一个store。

函数原型:createStore(reducer, [preloadedState], enhancer)

创建例子:

const store = createStore(
  rootReducer,
  initState,
);

Action

用于修改Store数据的数据载体。
其为js的Object,必须包含一个type字段,用于定义Action类型,用于Reducer进行识别处理。

通常会定义出type常量,然后实现一个Action生成函数,再配合dispatch()使用

export const ACT_LOAD_START = 'ACT_LOAD_START';
export function actLoadStart(data, key) {
  return { type: ACT_LOAD_START, data, key};
}

dispatch(actLoadStart({id: 123}, 'asdf'));

Reducer

接收state和action,然后根据action.type进行处理
函数例子

function todoReducer(state = {}, action){
    switch(action.type) {
        case TODO_ADD:
            return {...state, text: action.text}
        default:
            return state
    }
}

除了使用switch语句来写之外,还可以用字典的方式,我认为还是这种方式写来的舒服,而且scope干净

function todoReducer(state = {}, action){
    const handers = {
        [TODO_ADD](){
            return {...state, text: action.text}
        },
        [TODO_DEL](){
            return {...state, text: action.text}
        },
    }
    if(action.type in handers){
        return handers[action.type]();
    }

    return state;
}

异步Action(redux-thunk)

上面Action章节中的action生成函数,是同步的,假如需要从外部系统接收数据再保存到store的话,那么会造成线程阻塞
redux-thunk实现了异步机制

引用前端攻城小牛文章中的解释:

可以看出来redux-thunk最重要的思想,就是可以接受一个返回函数的action creator。如果这个action creator 返回的是一个函数,就执行它,如果不是,就按照原来的next(action)执行。
正因为这个action creator可以返回一个函数,那么就可以在这个函数中执行一些异步的操作。

也就是说,现在我们的action生成函数,可以返回一个函数了,该函数原型是

function (dispatch, getState)

功能性示例:


function setCurrentRole(role){ } function fetchUserRole(userId) { return (dispatch, getState) => { const user = getState()[userId]; return fetch('/role/${user.roleId}', {method: 'POST'}) .then(res => res.json()) .then(role => { return dispatch(setCurrentRole(role)) }) .catch(error => { console.log(error); }) } }

配置redux-thunk

import thunk from 'redux-thunk';

const store = createStore(
  rootReducer,
  initState,
  compose(
    applyMiddleware(thunk),
  ),
);

redux解耦: bindActionCreators

引用官方文档中的两段话:

惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。

这对于需要在服务端进行渲染的同构应用会有问题。多数情况下,你的每个请求都需要一个独立的 store 实例,这样你可以为它们提供不同的数据,但是在定义的时候绑定 action creator,你就只能使用一个唯一的 store 实例来对应所有请求了。

第二段话解释了解耦动机:服务端和客户端之间的组件复用

组件的mapStateToProps/mapDispatchToProps

利用redux的connect方法,可以实现组件的hack
函数原型:

function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

这里来看看mapStateToProps和mapDispatchToProps两个回调函数的原型

function mapStateToProps(state)
function mapDispatchToProps(dispatch)

mapDispatchToProps

我认为mapDispatchToProps是专门为redux解耦而设计的接口
配合bindActionCreators:

import Actions from './actions';

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch),
  };
}

mapStateToProps

用于将store中的一些值绑定到组件的props,多用于一些全局的东西,本人对此理解也不是很深。
例:

function mapStateToProps({users}) {
  return {
    users,
    userIds: users.map(e => e.id),
  };
}

绑定到组件

class Button extends React.PureComponent {
}

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(Button);

四、总结

可以看到,文中没有涉及半点css。没错,作为后端,css就像甲骨文。。。
不过就算不懂css,利用React框架,再利用如antd等UI框架,就可以做出强大的界面,这是我当初不曾想过的。
从接触JS到写下这篇文章,断断续续2个月,让我对JS的感觉发生了突变,当初看到《JavaScript权威指南》这本厚书是恐惧,现在是有点喜欢。
最喜欢的其实是React的component,真的是拿来即用,一方面对后端程序员非常友好,另一方面代码更加有层次,代码耦合性降低,维护更加方便。
当然这需要对代码与功能进行抽象,还有总体架构的设计,所以说门槛还是比较高的,但是高有高的好处嘛。

五、参考

react生命周期: https://www.cnblogs.com/jinzhou/p/9096825.html
setstate: https://reactjs.org/docs/react-component.html#setstate
createStore: https://cn.redux.js.org/docs/api/createStore.html
bindActionCreators: https://cn.redux.js.org/docs/api/bindActionCreators.html
redux-thunk: https://github.com/reduxjs/redux-thunk/blob/master/README.md
https://www.jianshu.com/p/a27ab19d3657


发表评论

电子邮件地址不会被公开。 必填项已用*标注