写给新人的React快速入门手册

基础

组件

React组件大体可分为三种写法 一种es6的class语法,继承React.Component类实现带有完整生命周期的组件javascript

import React, { Component } from 'react';

export default class SingleComponent extends Component {
  /* 包含一些生命周期函数、内部函数以及变量等 */
  render() {
    return (<div>{/**/}</div>)
  }
}
复制代码

第二种是无状态组件,也叫函数式组件html

const SingleComponent = (props) => (
  <div>{props.value}</div>
);
export default SingleComponent;
复制代码

还有一种较为特殊,叫高阶组件,严格来讲高阶组件只是用来包装以上两种组件的一个高阶函数java

const HighOrderComponent = (WrappedComponent) => {
  class Hoc extends Component {
    /*包含一些生命周期函数*/
    render() {
      return (<WrappedComponent {...this.props} />); } } return Hoc; } 复制代码

高阶组件的原理是接受一个组件并返回一个包装后的组件,能够在返回的组件里插入一些生命周期函数作相应的操做,高阶组件可使被包装的组件逻辑不受干扰从外部进行一些扩展react

props和state

react中组件自身的状态叫作state,在es6+的类组件中可使用很简单的语法进行初始化git

export default class Xxx extends Component {
  state = {
    name: 'sakura',
  }
  render() {
    const { name } = this.state;
    return (<div>{name}</div>);
  }
}
复制代码

state能够赋值给某个标签,若是须要更新state能够调用this.setState()传入一个对象,经过这个方法修改state以后绑定了相应值的元素也会触发渲染,这就是简单的数据绑定es6

不能经过this.state.name = 'xxx'的方式修改state,这样就会失去更新state同时相应元素改变的效果github

setState函数是react中较为重要也是使用频率较高的一个api,它接受最多两个参数,第一个参数是要修改的state对象,第二个参数为一个回调函数,会在state更新操做完成后自动调用,因此setState函数是异步的。 调用this.setState以后react并无马上更新state,而是将几回连续调用setState返回的对象合并到一块儿,以提升性能,如下代码可能不会产生指望的效果web

class SomeButton extends Component {
  state = {
    value: 1,
  }
  handleClick = () => {
    const { value } = this.state;
    this.setState({ value: value + 1 });
    this.setState({ value: value + 1 });
    this.setState({ value: value + 1 });
    this.setState({ value: value + 1 });
  }
  render() {
    const { value } = this.state;
    return (<div> <span>{vlaue}</span> <button onClick={this.handleClick}>click!</button> </div>);
  }
}
复制代码

实际上这里并无对value进行4次+1的操做,react会对这四次更新作一次合并,最终只保留一个结果,相似于redux

Object.assign({},
  { value: value + 1 },
  { value: value + 1 },
  { value: value + 1 },
);
复制代码

而且由于setState是异步的,因此不能在调用以后立马获取新的state,若是要用只能给setState传入第二个参数回调函数来获取api

/*省略部分代码*/
this.setState({
  value: 11,
}, () => {
  const { value } = this.state;
  console.log(value);
})
复制代码

props是由父元素所传递给给子元素的一个属性对象,用法一般像这样

class Parent extends Component {
  /*父组件的state中保存了一个value*/
  state = {
    value: 0,
  };

  handleIncrease = () => {
    const { value } = this.state;
    this.setState({ value: value + 1 });
  }

  render() {
    const { value } = this.state;
    // 经过props传递给子组件Child,并传递了一个函数,用于子组件点击后修改value
    return (<div> <Child value={value} increase={this.handleIncrease} /> </div>) } } // 子组件经过props获取value和increase函数 const Child = (props) => ( <div> <p>{props.value}</p> <button onClick={props.increase}>click!</button> </div> ); 复制代码

props像一个管道,父组件的状态经过props这个管道流向子组件,这个过程叫作单向数据流

react中修改state和props都会引发组件的从新渲染

组件的生命周期

生命周期是一组用来表示组件从渲染到卸载以及接收新的props以及state声明的特殊函数

react生命周期函数执行过程
这张图展现了react几个生命周期函数执行的过程,能够简单把组件的生命周期分为三个阶段,共包含9个生命周期函数,在不一样阶段组件会自动调用

  • 挂载
    • componentWillMount
    • render
    • componentDidMount
  • 更新
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • render
    • componentDidUpdate
  • 卸载
    • componentWillUnmount

挂载--componentWillMount

这个阶段组件准备开始渲染DOM节点,能够在这个方法里作一些请求之类的操做,可是由于组件尚未首次渲染完成,因此并不能拿到任何dom节点

挂载--render

正式渲染,这个方法返回须要渲染的dom节点,而且作数据绑定,这个方法里不能调用this.setState方法修改state,由于setState会触发从新渲染,致使再次调用render函数触发死循环

挂载--componentDidMount

这个阶段组件首次渲染已经完成,能够拿到真实的DOM节点,也能够在这个方法里作一些请求操做,或者绑定事件等等

更新--componentWillReceiveProps

当组件收到新的props和state且尚未执行render时会自动触发这个方法,这个阶段能够拿到新的props和state,某些状况下可能须要根据旧的props和新的props对比结果作一些相关操做,能够写在这个方法里,好比一个弹窗组件的弹出状态保存在父组件的state里经过props传给自身,判断这个弹窗弹出能够这样写

class Dialog extends Component {
  componentWillReveiceProps(nextProps) {
    const { dialogOpen } = this.props;
    if (nextProps.dialogOpen && nextProps.dialogOpen !== dialogOpen) {
      /*弹窗弹出*/
    }
  }
}
复制代码

更新--shouldComponentUpdate

shouldComponentUpdate是一个很是重要的api。react的组件更新过程通过以上几个阶段,到达这个阶段须要确认一次组件是否真的须要根据新的状态再次渲染,确认的依据就是对比新旧状态是否有所改变,若是没有改变则返回false,后面的生命周期函数不会执行,若是发生改变则返回true,继续执行后续生命周期,而react默认就返回true

因此能够得出shouldComponentUpdate能够用来优化性能,能够手动实现shouldComponentUpdate函数来对比先后状态的差别,从而阻止组件没必要要的重复渲染

class Demo extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return this.props.value !== nextProps.value;
  }
}
复制代码

这段代码是一个最简单的实现,经过判断this.props.valuenextProps.value是否相同来决定组件要不要从新渲染,可是实际项目中数据复杂多样,并不只仅是简单的基本类型,可能有对象、数组甚至是更深嵌套的对象,而数据嵌套越深就意味着这个方法里须要作更深层次的对比,这对react性能开销是极大的,因此官方更推荐使用Immutable.js来代替原生的JavaScript对象和数组

因为immutablejs自己是不可变的,若是须要修改状态则返回新的对象,也正由于修改后返回了新对象,因此在shouldComponentUpdate方法里只须要对比对象的引用就很容易得出结果,并不须要作深层次的对比。可是使用immutablejs则意味着增长学习成本,因此还须要作一些取舍

更新--componentWillUpdate

这个阶段是在收到新的状态而且shouldComponentUpdate肯定组件须要从新渲染而还未渲染以前自动调用的,在这个阶段依然能获取到新的props和state,是组件从新渲染前最后一次更新状态的机会

更新--render

根据新的状态从新渲染

更新--componentDidMount

从新渲染完毕

卸载--componentWillmount

组件被卸载以前,在这里能够清除定时器以及解除某些事件

组件通讯

不少业务场景中常常会涉及到父=>子组件或者是子=>父组件甚至同级组件间的通讯,父=>子组件通讯很是简单,经过props传给子组件就能够。而子=>父组件通讯则是大多数初学者常常碰到的问题 假设有个需求,子组件是一个下拉选择菜单,父组件是一个表单,在菜单选择一项以后须要将值传给父级表单组件,这是典型的子=>父组件传值的需求

const list = [
  { name: 'sakura', id: 'x0001' },
  { name: 'misaka', id: 'x0003' },
  { name: 'mikoto', id: 'x0005' },
  { name: 'react', id: 'x0002' },
];

class DropMenu extends Component {
  handleClick = (id) => {
    this.props.handleSelect(id);
  }

  render() {
    <MenuWrap>
      {list.map((v) => (
        <Menu key={v.name} onClick={() => this.handleClick(v.id)}>{v.name}</Menu>
      ))}
    </MenuWrap>
  }
}

class FormLayout extends Component {
  state = {
    selected: '',
  }
  handleMenuSelected = (id) => {
    this.setState({ selected: id });
  }
  render() {
    <div>
      <MenuWrap handleSelect={this.handleMenuSelected} />
    </div>
  }
}
复制代码

这个例子中,父组件FormLayout将一个函数传给子组件,子组件的Menu点击后调用这个函数并把值传进去,而父组件则收到了这个值,这就是简单的子=>父组件通讯

而对于更为复杂的同级甚至相似于叔侄关系的组件能够经过状态提高的方式互相通讯,简单来讲就是若是两个组件互不嵌套,没有父子关系,这种状况下,能够找到他们上层公用的父组件,将state存在这个父组件中,再经过props给两个组件传入相应的state以及对应的回调函数便可

路由

React中最经常使用的路由解决方案就是React-router,react-router迄今为止已经经历了四个大版本的迭代,每一版api变化较大,本文将按照最新版react-router-v4进行讲解

基本用法

使用路由,要先用Router组件将App包起来,并把history对象经过props传递进去,最新版本中history被单独分出一个包,使用的时候须要先引入。对于同级组件路由的切换,须要使用Switch组件将多个Route包起来,每当路由变动,只会渲染匹配到的一个组件

import ReactDOM from 'react-dom';
import createHistory from 'history/createBrowserHistory';
import { Router } from 'react-router';

import App from './App';

const history = createHistory();

ReactDOM.render(
  <Router history={history}> <App /> </Router>,
  element,
);

// App.js
//... 省略部分代码

import {
  Switch, Route,
} from 'react-router';

class App extends Component {
  render() {
    return (
      <div>
        <Switch>
          <Route exact path="/" component={Dashboard} />
          <Route path="/about" component={About} />
        </Switch>
      </div>
    );
  }
}
复制代码

CodesanBox在线示例

状态管理

关于单页面应用状态管理能够先阅读民工叔这篇文章单页应用的数据流方案探索

React生态圈的状态管理方案由facebook提出的flux架构为基础,并有多种不一样实现,而最为流行的两种是

flux架构

Flux

Flux is the application architecture that Facebook uses for building client-side web applications. It complements React's composable view components by utilizing a unidirectional data flow. It's more of a pattern rather than a formal framework, and you can start using Flux immediately without a lot of new code.

Flux是facebook用于构建web应用的一种架构,它经过使用单向数据流补充来补充React的组件,它只是一种模式,而不是一个正式的框架

首先,Flux将一个应用分为三个部分:

  • dispatcher
  • stores
  • views

dispatcher

dispatcher是管理Flux应用中全部数据流的中心枢纽,它的做用仅仅是将actions分发到stores,每个store都监听本身而且提供一个回调函数,当用户触发某个操做时,应用中的全部store都将经过监听的回调函数来接收这个操做

facebook官方实现的Dispatcher.js

stores

stores包含应用程序的状态和逻辑,相似于传统MVC中的model,stores用于存储应用程序中特定区域范围的状态

一个store向dispatcher注册一个事件并提供一个回调函数,这个回调函数能够接受action做为参数,而且基于actionType来区分并解释操做。在store中提供相应的数据更新函数,在确认更新完毕后广播一个事件用于应用程序根据新的状态来更新视图

// Facebook官方实现FluxReduceStore的用法
import { ReduceStore, Dispatcher } from 'flux';
import Immutable from 'immutable';
const dispatch = new Dispatcher();

class TodoStore extends ReduceStore {
  constructor() {
    super(dispatch);
  }
  getInitialState() {
    return Immutable.OrderedMap();
  }
  reduce(state, action) {
    switch(action.type) {
      case 'ADD_TODO':
        return state.set({
          id: 1000,
          text: action.text,
          complete: false,
        });
      default:
        return state;
    }
  }
}

export default new TodoStore();

复制代码

views

React提供了views所需的可组合以及能够自由的从新渲染的视图,在React最顶层组件里,经过某种粘合代码从stores中获取所需数据,并将数据经过props传递到它的子组件中,咱们就能够经过控制这个顶层组件的状态来管理页面任何部分的状态

Facebook官方实现中有一个FluxContainer.js用于链接store与react组件,并在store更新数据后刷新组件状态更新视图。基本原理是用一个高阶组件传入Stores和组件须要的state与方法以及组件自己,返回注入了state和action方法的组件,基本用法像这样

import TodoStore from './TodoStore';
import Container from 'flux';
import TodoActions from './TodoActions';

// 能够有多个store
const getStores = () => [TodoStore];

const getState = () => ({
  // 状态
  todos: TodoStore.getState(),

  // action
  onAdd: TodoActions.addTodo,
});

export default Container.createFunctional(App, getStore, getState);
复制代码

CodeSanbox在线示例 后续会补充flux官方实现的源码解析

Redux

Redux是由Dan Abramov对Flux架构的另外一种实现,它延续了flux架构中viewsstoredispatch的思想,并在这个基础上对其进行完善,将本来store中的reduce函数拆分为reducer,并将多个stores合并为一个store,使其更利于测试

redux
The Evolution of Flux Frameworks这篇文章,是他对原Flux架构的见解以及他的改进

The first change is to have the action creators return the dispatched action.What looked like this:

export function addTodo(text) {
  AppDispatcher.dispatch({
    type: ActionTypes.ADD_TODO,
    text: text
  });
}
复制代码

can look like this instead:

export function addTodo(text) {
  return {
    type: ActionTypes.ADD_TODO,
    text: text
  };
}
复制代码

stores拆分为单一store和多个reducer

const initialState = { todos: [] };
export default function TodoStore(state = initialState, action) {
  switch (action.type) {
  case ActionTypes.ADD_TODO:
    return { todos: state.todos.concat([action.text]) };
  default:
    return state;
}
复制代码

Redux把应用分为四个部分

  • views
  • action
  • reducer
  • store

views能够触发一个action,reducer函数内部根据action.type的不一样来对数据作相应的操做,最后返回一个新的state,store会将全部reducer返回的state组成一个state树,再经过订阅的事件函数更新给views

views

react组件做为应用中的视图层

action

action是一个简单的JavaScript对象,包含一个type属性以及action操做须要用到的参数,推荐使用actionCreator函数来返回一个action,actionCreator函数能够做为state传递给组件

function singleActionCreator(payload) {
  return {
    type: 'SINGLE_ACTION',
    paylaod,
  };
}
复制代码

reducer

reducer是一个纯函数,简单的根据指定输入返回相应的输出,reducer函数不该该有反作用,而且最终须要返回一个state对象,对于多个reducer,可使用combineReducer函数组合起来

function singleReducer(state = initialState, action) {
  switch(action.type) {
    case 'SINGLE_ACTION':
      return { ...state, value: action.paylaod };
    default:
      return state;
  }
}

function otherReducer(state = initialState, action) {
  switch(action.type) {
    case 'OTHER_ACTION':
      return { ...state, data: action.data };
    default:
      return state;
  }
}

const rootReducer = combineReducer([
  singleReducer,
  otherReducer,
]);

复制代码

store

redux中store只有一个,经过调用createStore传入reducer就能够建立一个store,而且这个store包含几个方法,分别是subscribe, dispatch,getState,以及replaceReducer,subscribe用于给state的更新注册一个回调函数,而dispatch用于手动触发一个action,getState能够获取当前的state树,replaceReducer用于替换reducer,要在react项目中使用redux,必须再结合react-redux

import { connect } from 'react-redux';
const store = createStore(rootReducer);

// App.js
class App extends Component {
  render() {
    return (
      <div> test </div>
    );
  }
}

const mapStateToProps = (state) => ({
  vlaue: state.value,
  data: state.data,
});

const mapDispatchToProps = (dispatch) => ({
  singleAction: () => dispatch(singleActionCreator());
});

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

// index.js
import { Provider } from 'react-redux';

ReactDOM.render(
  <Provider store={store}> <APP /> </Provider>,
  element,
);
复制代码

CodeSanbox在线示例

因本人技术水平有限,文中如有错误或纰漏欢迎大佬们指出

博客地址,不定时更新

相关文章
相关标签/搜索