React.js 新手快速入门 - 开山篇

著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。

在阅读以前,会有一些小伙伴以为疑惑,做者怎么写前端文章了呢,做者不是专一后端Java吗?这是怎么了?其实否则,在4年前,那个时候尚未流行vue和react,身为后端程序员的咱们,无论是java仍是php程序员,都是须要写前端的,不过那个时候的前端没有如今那么多东西。咱们通常叫美工画好静态页面,而后交给后端程序员,后端程序员在静态页面中加入js代码,或者把静态页面替换成jsp、velocity等静态模板语言的代码,一个动态效果的页面就完成了。随着互联网的不断发展,程序员工种的划分也愈来愈明细,如今的前端和做者曾经那个时候已经大不同了,不管是思想仍是语言风格,为了学习下如何本身制做页面,也为了感觉下前端代码的魅力,故选择了React.js 前端框架做为学习目标。其实前端颇有趣,有意思!身为后端程序员的你不打算了解一下嘛~php

准备工做

在学习 react 以前,咱们须要先安装对应的运行环境,工欲善其事必先利其器。首先安装好以下环境:css

不知道个人读者是否是彻底不懂前端,建议读者有一点点的 Html、Css、java script、es6 的基础,实在没有建议花个1~2天学习下。html

熟悉官方create-react-app脚手架

react 前端项目和咱们平时的java项目同样,都有其本身的项目结构,java的项目结构有IDE开发工具帮咱们生产,在本文中,咱们使用facebook 的 create-react-app 脚手架项目来帮咱们生成 react 项目结构,操做以下:前端

# 全局安装官方脚手架
npm i -g create-react-app 
# 初始化建立一个基于 react 的项目
create-react-app 01_jpview_class
# 设置 npm 下载镜像源为淘宝, 和设置 maven 仓库源一个意思
npm config set registry http://registry.npm.taobao.org
复制代码

这个时候就开始建立项目了,时间有点长,由于正在下载须要的插件和依赖包,完成后项目结构以下:

├── README.md        文档
├── package-lock.json
├── package.json      npm 依赖
├── public      静态资源
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src      源码
   ├── App.css
   ├── App.js      根组件
   ├── App.test.js      测试
   ├── index.css      全局样式
   ├── index.js      入口
   ├── logo.svg
   └── serviceWorker.js      pwa支持
复制代码

什么是JSX语法

删除src目录下的因此文件,新建一个 index.js文件,内容为vue

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App/>, document.querySelector('#root'))
复制代码

新建 App.js文件,内容为:java

import React, { Component } from "react";

export default class App extends Component{
    render(){
        return <div>
            <button>雷猴啊</button>
        </div>
    }
}
export default KaikebaCart
复制代码

上面的代码看起来会有感到困惑的地方,首先就是ReactDOM.render(<App />, document.querySelector('#root')); 看起来是js和html的混合体,这种语法被称之为JSX,其实际核心的逻辑彻底是js来实现的。node

在项目目录终端执行如下命令能够看到效果react

# 下载依赖包
npm install
# 启动运行项目
npm start
复制代码

学习react基础语法

如下全部代码均可以直接复制到 index.js文件中 体验效果git

React 组件

React 的世界里一切皆是组件,咱们使用class语法构建一个最基本的组件,组件的使用方式和HTML相同,组件的render函数返回渲染的一个JSX页面,而后使用ReactDom渲染到页面里程序员

import React from 'react';
import ReactDOM from 'react-dom';
// 继承React.Component表示App是一个组件
class App extends React.Component {
    render() {
      return <div> Hello React </div>
    }
  }
 // ReactDOM.render()方法把 App中的内容追加到 index.html 中 <div id="root">的标签上
ReactDOM.render(<App/>, document.querySelector('#root'))
复制代码

属性传递

React组件使用和html相似的方式传递参数,在组件内部,使用this.props获取全部的传递的参数,在JSX里使用变量,使用{}包裹

import React from 'react';
import ReactDOM from 'react-dom';
// 继承React.Component表示App是一个组件
class App extends React.Component {
    render() {
      // 获取<App name="React"> 传递过来的属性name值
      return <div> Hello {this.props.name} </div>
    }
  }
 // ReactDOM.render()方法把 App中的内容追加到 index.html 中 <div id="root">的标签上
ReactDOM.render(<App name="React" />, document.querySelector('#root'))
复制代码

JSX

JSX是一种js的语法扩展,表面上像HTML,本质上仍是经过babel转换为js执行,全部在JSX里可使用{}来写js的语法,JSX本质上就是转换为React.createElement在React内部构建虚拟Dom,最终渲染出页面

import React from 'react';
import ReactDOM from 'react-dom';
// 继承React.Component表示App是一个组件
class App extends React.Component {
    render() {
      return (
        <div>
          // {2+2} js的计算语法,结果为4
          Hello {this.props.name}, I am {2 + 2} years old
        </div>
      )
    }
  }
 // ReactDOM.render()方法把 App中的内容追加到 index.html 中 <div id="root">的标签上
ReactDOM.render(<App name="React" />, document.querySelector('#root'))
复制代码

State和事件绑定

咱们到如今为止尚未更新过UI页面,React内部经过this.state变量来维护内部的状态,而且经过this.stateState来修改状态,render里用到的state变量,也会自动渲染到UI,咱们如今constructor()来初始化state,在JSX语法里使用this.state.num获取,而后jsx里使用onClick绑定点击事件,注意这里须要在constructor()里使用bind()方法绑定this指向,而后内部调用this.setState修改值,注意这里不能写成this.state.num+1,而是要调用this.setState,设置并返回一个全新的num值。

import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
    constructor(props){
      super(props)
      // 初始化构造是设置内部状态 num值为 1 
      this.state = {
        num:1
      }
      // 把handleClick()方法绑定到当前对象Counter上
      this.handleClick = this.handleClick.bind(this)
    }
    handleClick(){
      // 改变内部状态 num 的值
      this.setState({
        num:this.state.num + 1
      })
    }
    render() {
      return (
        <div>
          <p>{this.state.num}</p>
            {/*{this.handleClick} js语法调用当前对象的handleClick()方法*/}
          <button onClick={this.handleClick}>click</button>
        </div>
      )
    }
  }

ReactDOM.render(<Counter/>, document.querySelector('#root'))
复制代码

生命周期

在组件内部存在一些特殊的方法,会在组件的不一样阶段执行,好比组件加载完毕后会执行componentDidMount函数,组件更新的时候,会执行shouldComponentUpdate函数,若是返回true的话,就会一次执行componentWillMountrendercomponentDidMount,若是返回false的话,就不会执行。

import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
    constructor(props){
      super(props)
      this.state = {
        num:1
      }
      this.handleClick = this.handleClick.bind(this)
    }
    // 生命方法--组件渲染完成,只执行一次
    componentDidMount(){
      console.log('componentDidMount 函数触发')
    }
    // 生命方法--避免组件重复或者无心义渲染
    shouldComponentUpdate(nextProps,nextState){
      if (nextState.num%2) {
        return true
      }
      return false
    }
    handleClick(){
      this.setState({
        num:this.state.num+1
      })
    }
    render() {
      return (
        <div>
          <p>{this.state.num}</p>
          <button onClick={this.handleClick}>click</button>
        </div>
      )
    }
  }

ReactDOM.render(<Counter/>, document.querySelector('#root'))
复制代码

生命周期流程图:

表单

用户想提交数据到后台,表单元素是最经常使用的,一个常见的表单由forminputlabel等标签构成,咱们经过onChange()方法控制value的值,最终经过state,让在的html input中输入内容和`React``关联起来。

import React from 'react';
import ReactDOM from 'react-dom';

class TodoList extends React.Component {
    constructor(props){
      super(props)
      this.state = {
        text:''
      }
      this.handleClick = this.handleClick.bind(this)
      this.handleChange = this.handleChange.bind(this)
    }
    handleClick(){
      // 若是内部状态 text有值,则把值清空''
      if (this.state.text) {
        this.setState({
          text:''
        })
      }
    }
    handleChange(e){
      // 获取事件元素input的值赋值给内部状态 text 中
      this.setState({
        text:e.target.value
      })
    }
    render() {
      return (
        <div>
          {/* 显示内部状态 text 的内容*/}
          {this.state.text}
          {/*input接收到输入值调用handleChange()方法*/}
          <input type="text" value={this.state.text} onChange={this.handleChange}/>
          {/*点击按钮调用handleClick()方法*/}
          <button onClick={this.handleClick}>clear</button>    
        </div>
      )
    }
  }
  
ReactDOM.render(<TodoList/>, document.querySelector('#root'))
复制代码

渲染列表

页面里序列化的数据,好比用户列表,都是一个数组,咱们经过map函数把数组直接映射为JSX,可是咱们直接渲染列表,打开console的时候会看到Each child in an array or iterator should have a unique "key" prop.报错。在渲染列表的时候,咱们须要每一个元素都有一个惟一的key属性,这样React在数据变化的时候,知道哪些dom应该发生变化 尤为注意key要惟一,建议每一个字段惟一id,或者使用索引

import React from 'react';
import ReactDOM from 'react-dom';

class TodoList extends React.Component {
    constructor(props){
      super(props)
      // 内部装填属性初始化值
      this.state = {
        todos:['Learn React','Learn Ant-design','Learn Koa'],
        text:''
      }
      this.handleClick = this.handleClick.bind(this)
      this.handleChange = this.handleChange.bind(this)
    }
    handleClick(){
      if (this.state.text) {
        this.setState(state=>({
           // 若是内部状态 text有值,追加到解构的todos数组后
          todos:[...state.todos,state.text],
           // 若是内部状态 text有值,则把值清空''
          text:''
        }))
      }
  
    }
    handleChange(e){
      // 获取事件元素input的值赋值给内部状态 text 中
      this.setState({
        text:e.target.value
      })
    }
    render() {
      return (
        <div>
          {/*input接收到输入值调用handleChange()方法*/}
          <input type="text" value={this.state.text} onChange={this.handleChange}/>
          {/*点击按钮调用handleClick()方法*/}
          <button onClick={this.handleClick}>add</button>
          <ul>
            {/*map()循环输出JSX内容给ReactDOM*/}
            {this.state.todos.map(v=>{
              return <li key={v}>{v}</li>
            })}
          </ul>
        </div>
      )
    }
  }

ReactDOM.render(<TodoList/>, document.querySelector('#root'))
复制代码

React16新增了什么

2017年9月27日,Facebook 官方发布了 React 16.0。相较于以前的 15.x 版本,v16是第一个核心模块重写了的版本,而且在异常处理,核心架构和服务端渲染方面都有更新。

  • render 函数支持返回数组和字符串
  • 异常处理,添加componentDidCatch钩子获取组件错误
  • 新的组件类型 portals 能够渲染当前容器dom以外的节点
  • 打包的文件体积减小 30%
  • 更换开源协议为MIT许可
  • Fiber架构,支持异步渲染
  • 更好的服务端渲染,支持字节流渲染
import React from 'react';
import ReactDOM from 'react-dom';

// 继承React.Component表示React16是一个组件
class React16 extends React.Component {
    // 构造器函数
    constructor(props){
      super(props)
      this.state={hasError:false}
    }
    // 生命周期函数
    componentDidCatch(error, info) {
      // 设置内部状态 hasError为true
      this.setState({ hasError: true })
    }
  
    render() {
      return (
        <div>
          {/*? : 是三目运算符*/}
          {this.state.hasError ? <div>出错了</div>:null}
          {/*使用组件ClickWithError和FeatureReturnFragments*/}
          <ClickWithError />
          <FeatureReturnFragments />
        </div>
      )
    }
  }
  // 继承React.Component表示ClickWithError是一个组件
  class ClickWithError extends React.Component{
     constructor(props){
      super(props)
      this.state = {error:false}
      // 绑定handleClick()方法到当前对象上
      this.handleClick = this.handleClick.bind(this)
    }
    handleClick(){
      // 触发调用时设置state.error值为true
      this.setState({
        error:true
      })
    }
    render() {
      if (this.state.error) {
        throw new Error('出错了!')
      }
      return <button onClick={this.handleClick}>抛出错误</button>
    }
  }
  // 继承React.Component表示FeatureReturnFragments是一个组件
  class FeatureReturnFragments extends React.Component{
    render(){
      return [
        <p key="key1">React很不错</p>,
        "文本1",
        <p key="key2">Antd-desing也很赞</p>,
        "文本2"
     ]
    }
  }
  
ReactDOM.render(<React16/>, document.querySelector('#root'))
复制代码

虚拟DOM

什么是DOM?—HTML DOM 教程

DOM操做成本实在是过高,因此有了在js里模拟和对比文档对象模型的方案,JSX里使用 reactcreateElement构建虚拟DOM,每次只要有修改,先对比js里面的虚拟dom树里的内容。 传统浏览器渲染流程图

虚拟DOM树结构图
参考:
react 中文官网
在线学习体验 react api

实战来总结

学完了api 的使用,是时候拿起武器开始真刀真枪的开干了,如图是实战的效果演示,具体的代码分析讲解能够直接在个人github上看到,就不在本文赘述了,我要传送代码仓库===> 项目代码地址

深刻理解生命周期

React v16.0版本以前

组件初始化阶段(initialization)

以下代码中类的构造方法constructor(),Test类继承了react Component这个基类,也就继承这个react的 基类,才能有render(),生命周期等方法可使用,这也说明为何函数组件不能使用这些方法的缘由。

super(props) 用来调用基类的构造方法constructor(), 也将父组件的props注入给子组件,供子组件读取(组件 中props属性只读不可写,state可写)。 而 constructor() 用来作一些组件的初始化工做,好比定义this.state的初始内 容。

import React, { Component } from 'react';
    class Test extends Component {
        constructor(props) {
        super(props);
    }
}
复制代码

组件的挂载阶段(Mounting)

此阶段分为componentWillMountrendercomponentDidMount三个时期。

  • componentWillMount:在组件挂载到DOM前调用,且只会被调用一次,在这里面调用this.setState不会引发组件的从新渲染,也能够把写在这里面的内容改写到constructor()中,因此在项目中不多这么使用。
  • render:根据组件的propsstate(不管二者是重传递或重赋值,不管值是否有变化,均可以引发组件从新render) ,内部return 一个React元素(描述组件,即UI),该元素不负责组件的实际渲染工做,以后由React自身根据此元素去渲染出页面DOM。render是纯函数 (Pure function:函数的返回结果只依赖于它的参数;函数执行过程里面没有反作用),不能在里面执行this.setState等操做,会有改变组件状态的反作用。
  • componentDidMount:组件挂载到DOM后调用,且只会被调用一次

组件的更新阶段(update)

在组件的更新阶段中,存在不少生命方法,从上图能够很直观的看到,有 componentWillReceivePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate

  • componentWillReceiveProps(nextProps):此方法只调用于props引发的组件更新过程当中,参数nextProps是父组件传给当前组件的新props。但父组件render 方法的调用不能保证重传给当前组件的props是有变化的,因此在此方法中根据nextPropsthis.props来查明重传 的props是否改变,以及若是改变了要执行啥,好比根据新的props调用this.setState出发当前组件的从新render
  • shouldComponentUpdate(nextProps,nextState):此方法经过比较nextPropsnextState及当前组件的this.propsthis.state,返回true时当前组件将继续执行更新过程,返回false则当前组件更新中止,以此可用来减小组件的没必要要渲染,优化组件性能。 这边也能够看出,就算componentWillReceiveProps()中执行了this.setState,更新了state,但在render前 (如shouldComponentUpdatecomponentWillUpdate),this.state依然指向更新前的state,否则nextState 及当前组件的this.state的对比就一直是true了。
  • componentWillUpdate(nextProps, nextState):此方法在调用render方法前执行,在这边可执行一些组件更新发生前的工做,通常较少用。
  • renderrender方法在上文讲过,这边只是从新调用。
  • componentDidUpdate(prevProps, prevState):此方法在组件更新后被调用,能够操做组件更新的DOMprevPropsprevState这两个参数指的是组件更新前的propsstate

在此阶段须要先明确下react组件更新机制。setState引发的state更新,或父组件从新render引发的props更新,更新后的stateprops相比较以前的结果,不管是否有变化,都将引发子组件的从新render。详细了解可看=>这篇文章 形成组件更新有两类(三种)状况:

  1. 父组件从新render 父组件从新render引发子组件从新render的状况有两种

    直接使用,每当父组件从新render致使的重传props,子组件都将直接跟着从新渲染,不管props是否有变化。可通 过shouldComponentUpdate方法控制优化。

    class Child extends Component {
       // 应该使用这个方法,不然不管props是否有变化都将会致使组件跟着从新渲染
       shouldComponentUpdate(nextProps){ 
           if(nextProps.someThings === this.props.someThings){
               return false
           }
       }
       render() {
           return <div>{this.props.someThings}</div>
       }
    }
    复制代码
  2. componentWillReceiveProps方法中,将props转换成本身的state
class Child extends Component {
   constructor(props) {
       super(props);
       this.state = {
           someThings: props.someThings
       };
   }
   componentWillReceiveProps(nextProps) { // 父组件重传props时就会调用这个方法
       this.setState({someThings: nextProps.someThings});
   }
   render() {
   return <div>{this.state.someThings}</div>
   }
}
复制代码

根据官网的描述: 在componentWillReceiveProps方法中,将props转换成本身的state 是由于componentWillReceiveProps中判断props是否变化了,若变化了,this.setState将引发state变化,从而引 起render,此时就不必再作第二次因重传props来引发render了,否则就重复作同样的渲染了。

  1. 组件自己调用setState,不管state有没有变化。能够经过shouldComponentUpdate方法控制优化。
shouldComponentUpdate() {
    // 组件是否须要更新,返回布尔值,优化点
    console.log("5.组件是否应该更新?");
    return true;
  }
复制代码

卸载阶段

此阶段只有一个生命周期方法:componentWillUnmount此方法在组件被卸载前调用,能够在这里执行一些清理工做,好比清楚组件中使用的定时器,清除componentDidMount中手动建立的DOM元素等,以免引发内存泄漏。

React v16.0版本以后(2019.11.20)

原来(React v16.0前)的生命周期在React v16推出的 Fiber以后就不合适了,由于若是要开启 async rendering, 在render函数以前的全部函数,都有可能被执行屡次。

原来(React v16.0前)的生命周期有哪些是在render前执行的呢?

  • componentWillMount
  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate

若是开发者开了async rendering,并且又在以上这些render前执行的生命周期方法作AJAX请求的话,那AJAX将被 无谓地屡次调用。。。明显不是咱们指望的结果。并且在componentWillMount里发起AJAX,无论多快获得结果 也赶不上首次render,并且componentWillMount在服务器端渲染也会被调用到(固然,也许这是预期的结 果),这样的IO操做放在componentDidMount里更合适。

禁止不能用比劝导开发者不要这样用的效果更好,因此除了shouldComponentUpdate,其余在render函数以前的 全部函数(componentWillMountcomponentWillReceivePropscomponentWillUpdate)都被 getDerivedStateFromProps替代。

也就是用一个静态函数getDerivedStateFromProps来取代被deprecate的几个生命周期函数,就是强制开发者在 render以前只作无反作用的操做,并且能作的操做局限在根据propsstate决定新的state

React v16.0刚推出的时候,是增长了一个componentDidCatch生命周期函数,这只是一个增量式修改,彻底不影 响原有生命周期函数;可是,到了React v16.3,大改动来了,引入了两个新的生命周期函数。

新的生命周期函数getDerivedStateFromProps和getSnapshotBeforeUpdate

  • getDerivedStateFromPropsgetDerivedStateFromProps 原本(React v16.3中)是只在建立和更新(由父组件引起部分),也就是否是不禁 父组件引起,那么getDerivedStateFromProps也不会被调用,如自身setState引起或者forceUpdate引起。这样的话理解起来有点乱,在React v16.4中改正了这一点,让getDerivedStateFromProps不管是Mounting仍是Updating,也不管是由于什么引发的Updating,所有都会被调用,具体可看React v16.4 的生命周期图。

static getDerivedStateFromProps(props, state) 在组件建立时和更新时的render方法以前调用,它应该返回 一个对象来更新状态,或者返回null来不更新任何内容。

  • getSnapshotBeforeUpdategetSnapshotBeforeUpdate() 被调用于render以后,能够读取但没法使用DOM的时候。它使您的组件能够在可 能更改以前从DOM捕获一些信息(例如滚动位置)。今生命周期返回的任何值都将做为参数传递给 componentDidUpdate()。
class ScrollingList extends React.Component {
   constructor(props) {
       super(props);
       this.listRef = React.createRef();
   }
   getSnapshotBeforeUpdate(prevProps, prevState) {
       //咱们是否要添加新的 items 到列表?
       // 捕捉滚动位置,以便咱们能够稍后调整滚动.
       if (prevProps.list.length < this.props.list.length) {
           const list = this.listRef.current;
           return list.scrollHeight - list.scrollTop;
       }
       return null;
   }
   componentDidUpdate(prevProps, prevState, snapshot) {
       //若是咱们有snapshot值, 咱们已经添加了 新的items.
       // 调整滚动以致于这些新的items 不会将旧items推出视图。
       // (这边的snapshot是 getSnapshotBeforeUpdate方法的返回值)
       if (snapshot !== null) {
           const list = this.listRef.current;
           list.scrollTop = list.scrollHeight - snapshot;
        }
   }
   render() {
       return (
           <div ref={this.listRef}>{/* ...contents... */}</div>
       );
   }
}
复制代码

扫码关注公众号,回复20191120获取本文全部源码

☞☞点击这里购买云服务器☜体验代码效果☜

相关文章
相关标签/搜索