React 作为时下最流行的框架之一,不可否认其在前端框架当中的地位,很多的项目也都在使用 React;作为一名 Front-end engineer,学习 React 无论是对于项目开发还是自身的水平都会有一定程度的帮助。

最近开始简单的学习了 React,所以在此简单的总结一下。

原文地址:初识 React

关于脚手架的简单使用

在网上看到了很多 React 的脚手架工具,这里我使用的是 create-react-app ,创建项目也比较简单。



  • 安装 create-react-app
    npm install -g create-react-app

  • 创建一个 React 项目
    create-react-app my-app

  • 启动项目
    npm start

项目结构

项目创建后,如图,目录结构也比较清晰:

clipboard.png

  • node_modules:存放项目依赖
  • public:存放项目入口文件
  • src:存放项目代码以及项目资源
  • package.json:项目依赖管理

关于目录结构,可以依据实际项目需求进行划分,如下图结构:

clipboard.png

  • assets -- 存放项目静态资源
  • components -- 存放组件
  • css -- 样式表
  • page -- 存放各个页面组件(也可称作父组件)

随着项目的复杂程度,项目结构划分的也更可加精细。

React 简单上手

写了一些 demo,总体感觉 React 还是很有意思的,因为之前接触过 Vue 的关系,所以觉得上手还是相对简单的,当然 React 和 Vue 也有很多不同点,如 React 组件有独立状态机,大大提高了组件的独立性、可操作性;React 支持 Jsx 语法,对于开发者来说可以一定程度提高开发效率;再配合 ES6 语法, 代码写起来会很舒服。

当然,随着对 React 更加深入的学习与了解,学习的梯度也会越来越高。作为开发者我们都知道 React 强大完整的生态体系,它已不单单是一个框架,而是已经发展为一个行业的解决方案。

简单总结上手碰到的坑

  1. 关于 getInitialState
  2. getDefaultProps 的疑惑
  3. 关于请求数据
  4. 关于事件传参
  5. React 中的 this
  6. 关于组件的生命周期
  7. 父子组件之间的传值
  8. react-router 的使用

关于 getInitialState

一开始看了阮大神的文章,殊不知在 ES6 中需要在 constructor 中定义 state ,导致了程序报错:

    constructor(props) {
        super(props);
        this.state = {flag: false};
        this.handle = () =>{
            //setState 是异步执行的,render时才会调用,可以写入回调函数
            this.setState({flag: !this.state.flag}, () => {
                //code
            });

        }
    }

getDefaultProps 的疑惑

在创建了一个组件后,使用 getDefaultProps 设置默认属性时,控制台报了这样一条警告:

clipboard.png

经查阅后了解到: getDefaultProps 方法是用在使用 React.createClass 方式创建组件的,应该使用 ES6 语法:

static defaultProps = {
    content: ''
}

关于请求数据

React 请求数据有很多方法如 $.ajax(), fetch api, axios, ajax() 等等,这里我简单使用了一下 fetch() 方法,fetch() 方法返回了一个 Promise 对象,reslove 时会返回 response 对象,一个最基本的用法:

  fetch(api)
  .then(function(response) {
    return response.json()
  }).then(function(json) {
    //resolve callback
    console.log('parsed json', json)
  }).catch(function(ex) {
    //reject callback
    console.log('parsing failed', ex)
  })

当然,因为 babel 对部分 ES7 的支持,也可以使用 await/async 进行请求,把异步代码当做同步代码来写,开发体验要比写回调好很多

//必须声明 async
async getData() {
    let response = await fetch(api)
    let data = await response.json();
    console.log(data);
}

注:在请求本地文件时需将资源放到 public 目录下,请求路径为 '/xx.json',也可以使用 import 引入文件。


关于事件传参

在为元素绑定事件的过程中,如果需要传参,可以使用 bind 方法进行传参:

<span className="g-data" onClick={this.handle.bind(this, this.props.source)}> 
    //code
 </span>

其中,bind 的第一个参数 this 是指改变函数运行时的指针,如:在 render() 中使用该方法,则 this 指向组件的实例,这样才能准确访问组件的属性和方法。

同样的,也可以使用箭头函数进行传参:

<span className="g-data" onClick={ () => this.handle(this.props.source) }> 
    //code
 </span>

React 中的 this

一开始在我为组件绑定事件的时候,发现在绑定事件中无法拿到 props 属性:

<span className="g-data" onClick={this.handle}> 
    //code
 </span>

最初我的理解是:绑定事件的函数中, this 应该是指向 class 创建的实例的。事实证明,我的理解是错误的:事件的回调函数是在全局环境下执行的,也就是 window 。于是我在 handle 中试着输出 this :

handle() {
    console.log(this); //undefined
}

输出了 undefined,又是一个大写的问号,于是我找了一下 ES6 的相关规范:在 class 中,定义类的时候默认使用严格模式,而在严格模式下,this 不能指向全局变量,因此变成了 undefined。所以,为了解决这个问题,大致有如下几种办法:

  • 在组件渲染时绑定 this
<span className="g-data" onClick={this.handle.bind(this)}> 
    //code
 </span>
  • 在 constructor 内绑定
constructor() {
    this.handle = this.handle.bind(this);
}
  • 使用箭头函数绑定
handle = () => {

}

或者在 DOM 结构中写箭头函数

<span className="g-data" onClick= { () => this.handle() } > 
    //code
 </span>

关于组件的生命周期

每一个组件都有几个你可以重写以让代码在处理环节的特定时期运行的“生命周期方法”。方法中带有前缀 will 的在特定环节之前被调用,而带有前缀 did 的方法则会在特定环节之后被调用。如:

  • componentWillMount( )

表示组件即将渲染之前会调用,即在 render() 之前调用。

  • componentDidMount( )

表示组件渲染完成立即调用,这个生命周期设置状态会触发重新渲染。

  • componentWillUnmount( )

表示组件将要被移除之前调用,在这里可以进行一系列的清理工作。

更多组件生命周期请移步 React 官方文档

父子组件之间的传值

  • 父组件给子组件传值

在父组件中为子组件添加属性和属性值,在子组件中通过 props 属性获得父组件对应的属性值。

//父组件
<div>
    <child title="This is title" />
</div>

//子组件
<div>{this.props.title}</div>
  • 子组件给父组件传值

可以在父组件的子组件中添加一个回调函数;子组件中设置状态值,当子组件中的值改变时,修改状态值会触发重渲,通过 props 拿到父组件的回调函数,并将新的状态值作为参数传入,这样父组件就可以拿到子组件的值并进行后续操作。

    //父组件
    constructor(props) {
        super(props)
        this.state = {
            childState: ''
        }
    }

    handleChange(childState) {
        this.setState({
            childState: childState
        })
    }

    render() {
        return (
            <div className="parent">
                <Child callBack={this.handleChange.bind(this)} />
            </div>
        )
    }
    
    //子组件
    constructor(props) {
        super(props)
        this.state = {
            childState: 'childState'
        }
    }

    handleChange(e) {
        //setState 是异步的,所以要先拿到 childState 的值,
        //或者写入 setState 的回调函数中
        var childState = e.target.value
        this.setState({
            childState : e.target.value
        })
        this.props.callBack(childState)
    }

    render() {
        return (
            <div className="child">
                <input type="text" onChange={this.handleChange.bind(this)} />
            </div>
        )
    }

react-router 的使用

一开始的时候,参考了一些文章试着使用了一下 react-router ,结果一直报错,后来发现版本更新导致部分语法改变,这里我使用的是 4.x 版本,一个比较基本的路由实现:
首先,引入 react-router:

import {
    BrowserRouter as Router,
    Route,
    Link
} from 'react-router-dom

接下来进行路由配置,告诉路由如何进行匹配以及匹配后如何执行代码:

<Router>
    <div>
        <Link to="/">Home</Link>
        <Link to="/About">About</Link>
        
        //exact(bool):为true时,则要求路径与location.pathname必须完全匹配;
        <Route exact path="/" component={Home}/>
        <Route path="/About" component={About}/>
    </div>
</Router>

在路由跳转到另一个页面时,如果当前页面存在路由跳转,则涉及到路由嵌套:

render() {
    let match = this.props.match
    return (
        <Router>
            <div>
                <Link to={match.url}>Tab One</Link>
                <Link to={`${match.url}/Two`}>Tab Two</Link>
                <Route exact path={match.url} render={()=>(<h3>1</h3>)} />
                //:param 可以理解为一种匹配模式,可以通过 params 拿到对应属性值
                <Route path={`${match.url}/:param`} component={Topic} />
            </div>
        </Router>
    )
}

match 对象中存储着路由路径的相关信息,通过 match 对象可以拿到一级路径,然后再根据二级路径进行匹配即可。

有些情况,在进行路由跳转时需要传递一些参数,当参数比较多的时候,通过路由路径传递参数就不是一个好的选择了,可以通过 query/state 传参:

//通过 location 对象取值
<Link to={{pathname:'/Message',query:{name:'react'},state:{name:'react'}}} >Message</Link>

//使用 js 传参
this.props.router.push({pathname:'/Message',query:{name:'react'}})

小结

通过简单上手 React ,我觉得用来开发项目很方便, 因为 React 使用了 virtual dom 所以在性能方面也比较给力,加上组件化开发、Jsx 和 ES6 语法的加持以及完善的生态体系,我想这大概就是 React 成为时下热门框架的原因之一吧。

参考文献

  1. React 入门实例教程
  2. React - 用于构建用户界面的 JavaScript 库
  3. 从 React 绑定 this,看 JS 语言发展和框架设计
  4. 深入浅出Fetch API
  5. 类 - JavaScript | MDN
  6. React 组件之间如何交流
  7. React Router 中文文档

水杯中的大海
13 声望1 粉丝

不忘初心,挑战未来