React入门指南(学习笔记)

4,511 阅读19分钟

React简介

首先不能否认React.js是全球最火的前端框架(Facebook推出的前端框架),国内的一二线互联网公司大部分都在使用React进行开发,比如阿里、美团、百度、去哪儿、网易 、知乎这样的一线互联网公司都把React作为前端主要技术栈。

React的社区也是非常强大的,随着React的普及也衍生出了更多有用的框架,比如ReactNative和React VR。React从13年开始推广,现在已经推出16RC(React Fiber)这个版本,性能和易用度上,都有很大的提升。

React优点总结

  • 生态强大:现在没有哪个框架比React的生态体系好的,几乎所有开发需求都有成熟的解决方案。
  • 上手简单: 你甚至可以在几个小时内就可以上手React技术,但是他的知识很广,你可能需要更多的时间来完全驾驭它。
  • 社区强大:你可以很容易的找到志同道合的人一起学习,因为使用它的人真的是太多了。

需要的前置知识

  • JavaScript基础:如果能会ES6就更好了

  • npm基础:你需要会使用npm的包管理


React开发环境搭建

  • 安装Node只需要进入Node网站,进行响应版本的下载,然后进行双击安装就可以了。
// 安装完成打开命令窗口输入
node -v   //出现版本号则安装成功
  • 脚手架的安装
// 安装react的脚手架工具
//create-react-app是React官方出的脚手架工具
npm install -g create-react-app  
  • 创建一个React项目
// 桌面创建一个react-demo的文件夹
// 进入当前文件夹,打开命令行
create-react-app demo   //用脚手架创建React项目
npm start   // 安装完成进入demo文件夹启动项目

项目根目录中的文件介绍

  • README.md :这个文件主要作用就是对项目的说明,已经默认写好了一些东西,你可以简单看看。如果是工作中,你可以把文件中的内容删除,自己来写这个文件,编写这个文件可以使用Markdown的语法来编写。

  • package.json: 这个文件是webpack配置和项目包管理文件,项目中依赖的第三方包(包的版本)和一些常用命令配置都在这个里边进行配置,当然脚手架已经为我们配置了一些了,目前位置,我们不需要改动。如果你对webpack了解,对这个一定也很熟悉。

  • package-lock.json:这个文件用一句话来解释,就是锁定安装时的版本号,并且需要上传到git,以保证其他人再npm install 时大家的依赖能保证一致。

  • gitignore: 这个是git的选择性上传的配置文件,比如一会要介绍的node_modules文件夹,就需要配置不上传。

  • node_modules:这个文件夹就是我们项目的依赖包,到目前位置,脚手架已经都给我们下载好了,你不需要单独安装什么。

  • public:公共文件,里边有公用模板和图标等一些东西。

  • src: 主要代码编写文件,这个文件夹里的文件对我们来说最重要,都需要我们掌握。

public文件夹介绍

这个文件都是一些项目使用的公共文件,也就是说都是共用的

  • favicon.ico: 这个是网站或者说项目的图标,一般在浏览器标签页的左上角显示。

  • index.html: 首页的模板文件

  • mainifest.json:移动端配置文件

src文件夹介绍

这个目录里边放的是我们开放的源代码,我们平时操作做最多的目录。

  • index.js: 这个就是项目的入口文件

  • index.css:这个是index.js里的CSS文件。

  • app.js: 这个文件相当于一个方法模块,也是一个简单的模块化编程。

  • serviceWorker.js:这个是用于写移动端开发的,PWA必须用到这个文件,有了这个文件,就相当于有了离线浏览的功能。


组件的介绍

入口文件的编写

写一个项目的时候一般要从入口文件进行编写的,在src目录下,index.js文件就是入口文件

import React from 'react' // 引入react
import ReactDOM from 'react-dom' // 引入react-dom
import App from './App' // 引入App模块
ReactDOM.render(<App />,document.getElementById('root')) // 将App模块渲染到了 root ID上面

App组件的编写

import React from 'react'
// JSX语法
class App extends React.Component{
    render(){
        return (
            <div>
                我是APP组件
            </div>
        )
    }
}
export default App;

React中JSX语法简介

JSX就是Javascript和XML结合的一种格式。React发明了JSX,
可以方便的利用HTML语法来创建虚拟DOM,当遇到<,JSX就当作HTML解析,
遇到{就当JavaScript解析.

组件和普通JSX语法区别

这个说起来也只有简单的一句话,就是你自定义的组件必须首写字母要进行大写,而JSX是小写字母开头的。

JSX中使用三元运算符

在JSX中也是可以使用js语法的

import React from 'react'
// JSX语法
class App extends React.Component{
    render(){
        return (
            <div>
                <div>{false ? '不显示我' : '显示的我'}</div>
                我是APP组件
            </div>
        )
    }
}
export default App;

组件外层包裹原则

react和vue组件模板的原则一样,最外层必须有且只有一个元素,多了会报错

下面这个列子就是一个错误的,因为最外层有了两个元素

import React from 'react'
// JSX语法
class App extends React.Component{
    render(){
        return (
            <div></div>
            <div>
                <div>{false ? '不显示我' : '显示的我'}</div>
                我是APP组件
            </div>
        )
    }
}
export default App;

Fragment标签

加上最外层的DIV,组件就是完全正常的,但是你的布局就偏不需要这个最外层的标签怎么办?比如我们在作Flex布局的时候,外层还真的不能有包裹元素。这种矛盾其实React16已经有所考虑了,为我们准备了标签。

import React from 'react'
// JSX语法
class App extends React.Component{
    render(){
        return (
            <React.Fragment>
                <div>{false ? '不显示我' : '显示的我'}</div>
            </React.Fragment>
        )
    }
}
export default App;

这时候再去浏览器的Elements中查看,就会发现已经没有外层的包裹了。


响应式设计和数据的绑定

React不建议你直接操作DOM元素,而是要通过数据进行驱动,改变界面中的效果。React会根据数据的变化,自动的帮助你完成界面的改变。所以在写React代码时,你无需关注DOM相关的操作,只需要关注数据的操作就可以了(这也是React如此受欢迎的主要原因,大大加快了我们的开发速度)。

import React from 'react'
// JSX语法
class App extends React.Component{
    constructor(props){
    super(props) //调用父类的构造函数,固定写法
        this.state={
            myName: '只会番茄炒蛋',
            list: ['番茄', '炒蛋']
        }
    }
    render(){
        return (
            <React.Fragment>
                <div>{this.state.myName}</div>
                <ul>
                    {
                        this.state.list.map((item,index) => {
                            return (
                                <li key={item + index}>{item}</li>
                            )
                        })
                    }
                </ul>
            </React.Fragment>
        )
    }
}
export default App;

绑定事件

import React from 'react'
// JSX语法
class App extends React.Component{
    constructor(props){
    super(props) //调用父类的构造函数,固定写法
        this.state={
            myName: '只会番茄炒蛋',
            list: ['番茄', '炒蛋']
        }
        this.changeBtn = this.changeBtn.bind(this)
    }
    render(){
        return (
            <React.Fragment>
                <div>{this.state.myName}</div>
                <button onClick={this.changeBtn}>点击事件</button>
            </React.Fragment>
        )
    }
    changeBtn () {
        // 这里要注意,如果没有绑定this这里的this是undefined
        // 通过setState方法去改变数据,保证页面会渲染改变后的内容
        this.setState({
            myName: '我的点击按钮后被改变的值'
        })
    }
}
export default App;

JSX防踩坑的几个地方

JSX代码注释

以下两种方式书写代码注释

 {/* 正确注释的写法 */}
 { // 正确注释的写法 }

JSX中的class陷阱

它是防止和js中的class类名冲突, 所以JSX中都是用className

<input class="input"/>  // 错误的写法
<input className="input"/>  // 正确的写法

JSX中的html解析问题

如果想在文本框里输入一个h1标签,并进行渲染。默认是不会生效的,只会把h1标签打印到页面上,这并不是我想要的。如果工作中有这种需求,可以使用dangerouslySetInnerHTML属性解决。具体代码如下:

<li dangerouslySetInnerHTML={{__html:item}}> {<h1>我会变成h1号字体</h1>}</li>

JSX中标签的坑

JSX中label的坑,也算是比较大的一个坑,label是html中的一个辅助标签,也是非常有用的一个标签。

<div>
    <label>加入服务:</label>
    <input className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
    <button onClick={this.addList.bind(this)}> 增加服务 </button>
</div>

这时候想点击“加入服务”直接可以激活文本框,方便输入。按照html的原思想,是直接加ID就可以了。代码如下:

<div>
    <label for="jspang">加入服务:</label>
    <input id="jspang" className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
    <button onClick={this.addList.bind(this)}> 增加服务 </button>
</div>

这时候你浏览效果虽然可以正常,但console里还是有红色警告提示的。大概意思是不能使用for.它容易和javascript里的for循环混淆,会提示你使用htmlfor。

<div>
    <label htmlFor="jspang">加入服务:</label>
    <input id="jspang" className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
    <button onClick={this.addList.bind(this)}> 增加服务 </button>
</div>

组件的拆分

父组件如何使用子组件

// 子组件
import React from 'react'
// JSX语法
class childItem extends React.Component{
    return (
        <React.Fragment>
            <div>我是子组件</div>
        </React.Fragment>
    )
}
export default childItem;
// 父组件
import React from 'react'
// 引入子组件
import childItem from './childItem'
// JSX语法
class parentItem extends React.Component{
    return (
        <React.Fragment>
            {/* 使用了子组件 */}
            <childItem/>
        </React.Fragment>
    )
}
export default parentItem;

父子组件的传值

父组件通过自定义属性将要传递的值传递给子组件

// 父组件
import React from 'react'
// 引入子组件
import childItem from './childItem'
// JSX语法
class parentItem extends React.Component{
    constractor (props) {
        super(props)
        this.state = {
            myName: '只会番茄炒蛋'
        }
    }
    return (
        <React.Fragment>
            {/* 使用了子组件 */}
            <childItem myName={this.state.myName}/>
        </React.Fragment>
    )
}
export default parentItem;

子组件通过this.props.XXX接受父组件传递归来的值

// 子组件
import React from 'react'
// JSX语法
class childItem extends React.Component{
    return (
        <React.Fragment>
            <div>{this.props.myName}</div>
        </React.Fragment>
    )
}
export default childItem;

父组件向子组件传递内容,靠属性的形式传递。

子组件向父组件传递数据

切记单项数据流!!!

React有明确规定,子组件时不能操作父组件里的数据的,所以需要借助一个父组件的方法,来修改父组件的内容。

// 子组件
import React from 'react'
// JSX语法
class childItem extends React.Component{
    constructor (props) {
        super(props)
        this.changeParent = this.changeParent.bind(this)
    }
    return (
        <React.Fragment>
            <div onClick={this.changeParent}>{this.props.myName}</div>
        </React.Fragment>
    )
    changeParent () {
        // 调用父组件传递过来的方法
        this.props.pranentChange()
    }
}
export default childItem;
// 父组件
import React from 'react'
// 引入子组件
import childItem from './childItem'
// JSX语法
class parentItem extends React.Component{
    constractor (props) {
        super(props)
        this.state = {
            myName: '只会番茄炒蛋'
        }
        this.pranentChange = this.pranentChange.bind(this)
    }
    return (
        <React.Fragment>
            {/* 使用了子组件 */}
            <childItem 
                pranentChange={this.pranentChange}
                myName={this.state.myName}
            />
        </React.Fragment>
    )
    pranentChange () {
        this.setState({
            myName: '番茄炒蛋少放糖'
        })
    }
}
export default parentItem;

PropTypes校验传递值

在父组件向子组件传递数据时,使用了属性的方式,也就是props,但是没有任何的限制。这在工作中时完全不允许的,因为大型项目,如果你不校验,后期会变的异常混乱,业务逻辑也没办法保证。

PropTypes的应用

普通效验

// 子组件
import React from 'react'
// 引入效验
import PropTypes from 'prop-types'
// JSX语法
class childItem extends React.Component{
    constructor (props) {
        super(props)
        this.changeParent = this.changeParent.bind(this)
    }
    return (
        <React.Fragment>
            <div onClick={this.changeParent}>{this.props.myName}</div>
        </React.Fragment>
    )
    changeParent () {
        // 调用父组件传递过来的方法
        this.props.pranentChange()
    }
}
childItem.prorTypes = {
    myName: PropTypes.string, // 效验传入的内容必须是一个字符串
    pranentChange: PropTypes.func // 效验传入的内容必须是一个function
}
export default childItem;

注意: 这里添加效验以后,如果不按照效验规则传入内容会有warning警告

必传值的效验---isRequired

例如这里加了一个newName的属性,并且放入JSX中,就算父组件不传递这个值也不会报错的

// 子组件
render() { 
    return ( 
        <div >{this.props.newName}</div>
    );
}

但是怎样避免必须传递newName这个属性值?如果不传递就报错,这就需要使用isRequired关键字了,它表示必须进行传递,如果不传递就报错。

childItem.prorTypes = {
    // 效验传入的内容必须是一个字符串并且必须传入值
    newName:PropTypes.string.isRequired, 
}

使用默认值---defaultProps

有时候子组件是需要有一个默认的值,如果父组件不传就用默认的,如果传了就用父组件传递过来的值,defalutProps就可以实现默认值的功能,比如现在把newName的默认值设置成"只会番茄炒蛋" 。

childItem.defaultProps = {
    // 子组件使用父组件传递的值,但是他自己有个默认的值
    newName:'只会番茄炒蛋'
}

ref的使用方法

在编写组件中的方法时,经常会遇到语义化很模糊的代码,这对于团队开发是一个很大的问题。因为review代码或者合作时都会影响开发效率。或者到这核心成员离开,项目倒闭的严重影响。所以我们必须重视react代码当中的语义化。ref是个不错的工具

import React from 'react'
// JSX语法
class parentItem extends React.Component{
    constractor (props) {
        super(props)
        this.state = {
            myName: '只会番茄炒蛋'
        }
        this.inputChange = this.inputChange.bind(this)
    }
    return (
        <React.Fragment>
            <input 
                value={this.state.myName}
                onChange={this.inputChange}
            ></input>
        </React.Fragment>
    )
    inputChange (e) {
        console.log(e.target.value)
    }
}
export default parentItem;

想要获取上面input输入的值,必须通过e.target.value,这样并不直观,也不好看,这中情况可以使用ref来进行解决

import React from 'react'
// JSX语法
class parentItem extends React.Component{
    constractor (props) {
        super(props)
        this.state = {
            myName: '只会番茄炒蛋'
        }
        this.inputChange = this.inputChange.bind(this)
    }
    return (
        <React.Fragment>
            <input 
                value={this.state.myName}
                onChange={this.inputChange}
                ref={input => {this.input = input}}
            ></input>
        </React.Fragment>
    )
    inputChange (e) {
        console.log(this.input.value)
    }
}
export default parentItem;

这就使代码变得语义化和优雅的多。但是是不建议用ref这样操作的,因为React的是数据驱动的,所以用ref会出现各种问题。

ref使用中的坑

比如现在我们要用ref绑定获取子元素的数量,可以先用ref进行绑定。

<ul ref={(ul)=>{this.ul=ul}}>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>  

这时候如果我们动态添加或者减少li的数量,并在方法中打印li的数量,发现返回的数量并不准确,这个坑是因为React中更新setState是一个异步函数所造成的。也就是这个setState在代码执行是有一个时间的,如果你真的想了解清楚,你需要对什么是虚拟DOM有一个了解。简单的说,就是因为是异步,还没等虚拟Dom渲染,我们的console.log就已经执行了。

那这个代码怎么编写才会完全正常那,其实setState方法提供了一个回调函数,也就是它的第二个函数。下面这样写就可以实现我们想要的方法了。

增加/删除(){
    this.setState({
        list:改变后的数据,
    },()=>{
        console.log(this.ul.querySelectorAll('li').length)
    })
}

生命周期

什么是生命周期函数

生命周期函数指在某一个时刻组件会自动调用执行的函数

挂载

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  • constructor()
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,
应在其他语句之前前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。

通常,在 React 中,构造函数仅用于以下两种情况:
* 通过给 this.state 赋值对象来初始化内部 state。
* 为事件处理函数绑定实例

在 constructor() 函数中不要调用 setState() 方法。
如果你的组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始 state:
  • static getDerivedStateFromProps()
getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
  • render()
页面state或props发生变化时执行。
  • componentDidMount()
组件挂载完成时被执行。一般也在这里发起网路请求

更新

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • static getDerivedStateFromProps()
// 不常用的生命周期方法
getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
  • shouldComponentUpdate()
// 不常用的生命周期方法
根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。
  • render()
render() 方法是 class 组件中唯一必须实现的方法。
当 render 被调用时,它会检查 this.props 和 this.state 的变化
  • getSnapshotBeforeUpdate()
// 不常用的生命周期方法
getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
  • componentDidUpdate()
componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。

卸载

当组件从 DOM 中移除时会调用如下方法:

  • componentWillUnmount()
componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,
例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

错误处理

当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

  • static getDerivedStateFromError()
// 不常用的生命周期方法
此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state
  • componentDidCatch()
// 不常用的生命周期方法
componentDidCatch() 会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况:

React中的axios数据请求

安装axios

npm install  axios -S

使用

// 那个组件使用在那个页面引入
import axios from 'axios'

引入后,可以在componentDidMount生命周期函数里请求ajax,我也建议在componentDidMount函数里执行,因为在render里执行,会出现很多问题,比如一直循环渲染;在componentWillMount里执行,在使用RN时,又会有冲突。所以强烈建议在componentDidMount函数里作ajax请求。

componentDidMount(){
    axios.post('https://www.easy-mock.com/mock/5d50245e710c4d0d04d78d19/echart')
        .then((res)=>{console.log('axios 获取数据成功:'+JSON.stringify(res))  })
        .catch((error)=>{console.log('axios 获取数据失败'+error)})
}

动画react-transition-group

React有着极好的开发生态,开发需要的任何基本需求都可以找到官方或大神造的轮子,动画这种必不可少的东西当然也不例外,React生态中有很多第三方的动画组件,学习一下react-transition-group动画组件。可以满足日常动画开发需求。 推荐的最重要理由是:这个也是react官方提供的动画过渡库,有着完善的API文档)。

安装react-transition-group

npm install react-transition-group -S

安装好后,你可以先去github上来看一下文档,他是有着三个核心库(或者叫组件)。

  • Transition
  • CSSTransition
  • TransitionGroup

使用CSSTransition

先用import进行引入,代码如下:

import { CSSTransition } from 'react-transition-group'

引入后便可以使用了,使用的方法就和使用自定义组件一样,直接写,而且不再需要管理className了,这部分由CSSTransition进行管理。

render() { 
    return ( 
        <div>
            <CSSTransition 
                in={this.state.isShow}   //用于判断是否出现的状态
                timeout={2000}           //动画持续时间
                classNames="boss-text"   //className值,防止重复
            >
                <div>BOSS级人物-孙悟空</div>
            </CSSTransition>
            <div><button onClick={this.toToggole}>召唤Boss</button></div>
        </div>
        );
}

需要注意的是classNames这个属性是由s的,如果你忘记写,会和原来的ClassName混淆出错,这个一定要注意。

  • xxx-enter: 进入(入场)前的CSS样式;
  • xxx-enter-active:进入动画直到完成时之前的CSS样式;
  • xxx-enter-done:进入完成时的CSS样式;
  • xxx-exit:退出(出场)前的CSS样式;
  • xxx-exit-active:退出动画知道完成时之前的的CSS样式。
  • xxx-exit-done:退出完成时的CSS样式。
.input {border:3px solid #ae7000}

.boss-text-enter{
    opacity: 0;
}
.boss-text-enter-active{
    opacity: 1;
    transition: opacity 2000ms;

}
.boss-text-enter-done{
    opacity: 1;
}
.boss-text-exit{
    opacity: 1;
}
.boss-text-exit-active{
    opacity: 0;
    transition: opacity 2000ms;

}
.boss-text-exit-done{
    opacity: 0;
}
unmountOnExit 属性

CSSTransition加上unmountOnExit属性,加上这个的意思是在元素退场时,自动把DOM也删除,这是以前用CSS动画没办法做到的。

render() { 
    return ( 
        <div>
            <CSSTransition 
                in={this.state.isShow}   //用于判断是否出现的状态
                timeout={2000}           //动画持续时间
                classNames="boss-text"   //className值,防止重复
                unmountOnExit
            >
                <div>BOSS级人物-孙悟空</div>
            </CSSTransition>
            <div><button onClick={this.toToggole}>召唤Boss</button></div>
        </div>
        );
}

使用TransitionGroup

它就是负责多个DOM元素的动画的,我们还是拿小姐姐这个案例作例子,现在可以添加任何的服务项目,但是都是直接出现的,没有任何动画,现在就给它添加上动画。添加动画,先引入transitionGrop。

import {CSSTransition , TransitionGroup} from 'react-transition-group'

引入之后,就可以使用这个组件了,方法是在外层增加标签。

<ul ref={(ul)=>{this.ul=ul}}>
    <TransitionGroup>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </TransitionGroup>
</ul> 

但是只有这个是不够的,你还是需要加入,来定义动画。

<ul ref={(ul)=>{this.ul=ul}}>
    <TransitionGroup>
        <CSSTransition
            timeout={1000}
            classNames='boss-text'
            unmountOnExit
            appear={true}
            key={index+item}
        >
            <li>1</li>
        </CSSTransition>
        <CSSTransition
            timeout={1000}
            classNames='boss-text'
            unmountOnExit
            appear={true}
            key={index+item}
        >
            <li>2</li>
        </CSSTransition>
    </TransitionGroup>
</ul> 

React动画还有很多知识,能做出很多酷炫的效果,完全可以单独分出来一个岗位,我在工作中用的都是比较简单的动画,用react-transition-group动画已经完全可以满足我的日常开发需求了


以上大部分内容和讲解都来来自技术胖的博客,结合了自己对react的理解。