为了账号安全,请及时绑定邮箱和手机立即绑定

【React.js模仿大众点评webapp】实战教程(2)React 基础


React 基础知识介绍

本章节会介绍一些 React 的基础知识和基本用法。已经入门 React 基础的同学,可以简单看看这篇文档并略过视频内容。React 零基础的同学还建议去慕课网学习React入门基础

另外,本教程的代码将全部使用 es6 语法,教程中我会介绍一些用到的 es6 语法,但是不会从头讲解了,推荐阅读es6入门

hello world

以下是一个最简单的demo,将一个最简单的组件渲染到页面上。

import React from 'react'
import { render } from 'react-dom'

// 定义组件
class Hello extends React.Component {
    render() {
        // return 里面写jsx语法
        return (
            <p>hello world</p>
        )
    }
}

// 渲染组件到页面
render(
    <Hello/>,
    document.getElementById('root')
)

深入一下,这里import React from 'react'引用的是什么?

这里的'react'对应的就是./package.json文件中dependencies中的'react',即在该目录下用npm install安装的 react 。npm 安装的 react 的物理文件是存放在 ./node_modules/react中的,因此引用的东西肯定就在这个文件夹里面。

打开./node_modules/react/package.json找到"main": "react.js",,这里的main即指定了入口文件,即./node_modules/react/react.js这个文件。那么,问题的答案自然就出来了。

jsx 语法

React 里面写模板要使用 jsx 语法,它其实和 html 很相似但是又有那么几点不一样。下面简单介绍一下 jsx 语法的一些特点:

使用一个父节点包裹

jsx 中不能一次性返回零散的多个节点,如果有多个请包涵在一个节点中。例如,

// 三个 <p> 外面必须再包裹一层 <div>
return (
  <div>
    <p>段落1</p>
    <p>段落2</p>
    <p>段落3</p>
  </div>
)

再例如:

// { } 中返回的两个 <p> 也要用 <div> 包裹
return (
  <div>
    <p>段落1</p>
    {
      true 
      ? <p>true</p>
      : <div>
        <p>false 1</p>
        <p>false 2</p>
      </div>
    }
  </div>
)

注释

jsx 中用{/* */}的注释形式

        return (
            // jsx 外面的注释
            <div>
                {/* jsx 里面的注释 */}
                <p>hello world</p>
            </div>
        )

样式

对应 html 的两种形式,jsx 的样式可以这样写:
css样式:<p className="class1">hello world</p>,注意这里是className,而 html 中是class
内联样式:<p style={{display: 'block', fontSize: '20px'}}>hello world</p>,注意这里的{{...}},还有fontSize的驼峰式写法

事件

拿 click 事件为例,要在标签上绑定 click 事件,可以这样写

class Hello extends React.Component {
    render() {
        return (
            <p onClick={this.clickHandler.bind(this)}>hello world</p>
        )
    }

    clickHandler(e) {
        // e 即js中的事件对象,例如 e.preventDefault()
        // 函数执行时 this 即组件本身,因为上面的 .bind(this)
        console.log(Date.now())
    }
}

注意,onClick是驼峰式写法,以及.bind(this)的作用

循环

在 jsx 中使用循环,一般会用到Array.prototype.map(来自ES5标准)

class Hello extends React.Component {
    render() {
        const arr = ['a', 'b', 'c']
        return (
            <div>
                {arr.map((item, index) => {
                    return <p key={index}>this is {item}</p>
                })}
            </div>
        )
    }
}

注意,arr.map是包裹在{}中的,key={index}有助于React的渲染优化,jsx中的{}可放一个可执行的 js 程序或者变量

判断

jsx中使用判断一般会用到三元表达式(表达式也是放在{}中的),例如:

return (
  <div>
    <p>段落1</p>
    {
      true 
      ? <p>true</p>
      : <p>false</p>
      </div>
    }
  </div>
)

也可以这样使用:

<p style={{display: true ? 'block' ? 'none'}}>hello world</p>

代码分离

之前的demo代码都是在一个文件中,实际开发中不可能是这样子的,因此这里就先把组件的代码给拆分开。我们将使用 es6 的模块管理规范。

page 层

创建./app/containers/Hello/index.jsx文件,将之前创建组件代码复制进去

import React from 'react'

class Hello extends React.Component {
    render() {
        return (
             <p>hello world</p>
        )
    }
}

export default Hello

然后./app/index.jsx中代码就可以这样写。

import Hello from './containers/Hello';

render(
    <Hello/>,
    document.getElementById('root')
)

注意,代码import Hello from './containers/Hello';这里可以写成./containers/Hello/index.jsx也可以写成./containers/Hello/index

subpage 层

如果Hello组件再稍微复杂一点,那么把代码都放一块也会变得复杂,接下来我们再拆分。

创建./app/containers/Hello/subpage目录,然后在其下创建三个文件Carousel.jsx Recommend.jsx List.jsx,分别写入相应的代码(看代码文件即可),然后./app/containers/Hello/index.js中即可这样写

import Carousel from './subpage/Carousel'
import Recommend from './subpage/Recommend'
import List from './subpage/List'

class Hello extends React.Component {
    render() {
        return (
            <div>
                <p>hello world</p>
                <hr/>
                <Carousel/>
                <Recommend/>
                <List/>
            </div>
        )
    }
}

注意,这里import.jsx后缀省略了。

component 层

以上介绍的是页面和复杂页面的拆分,但那都是页面层级的,即page层。这里复杂页面拆分为subpage其实没啥特别的,就是把复杂页面的代码拆分一下,会更加符合开放封闭原则。而且,只有复杂页面才有必要去拆分,简单页面根本没必要拆分。因此,无论是page还是subpage它都是页面级别的。

页面的特点是其独特性,一个页面就需要创建一个文件(如果两个页面可以共用一个文件,这是设计不合理,得治)。而页面其中的内容,就不一定是这样子了。例如,现在的APP每个页面最上面都会有个 header ,即可以显示标题,可以返回。每个页面都有,样子差不多,难道我们要为每个页面都做一个?——当然不是。

创建./app/components/Header/index.jsx文件,简单写入一个组件的代码(见源码文件),然后在./app/containers/index.jsx中引用

import Header from '../../components/Header'

class Hello extends React.Component {
    render() {
        return (
            <div>
                <Header/>
                {/* 省略其他内容 */}
            </div>
        )
    }
}

Hello 页面会用到 Header,以后的其他页面也会用到 Header ,我们把多个页面都可能用到的功能,封装到一个组件中,代码放在./app/components下。

数据传递 & 数据变化

props

接着刚才 Header 的话题往下说,每个页面都会使用 Header ,但是 Header 上显示的标题每个页面肯定是不一样的。我们需要这样解决:页面中引用Header时,这样写 <Header title="Hello页面"/>,即给 Header 组件设置一个 title 属性。而在 Header 组件中可以这样取到

    render() {
        return (
             <p>{this.props.title}</p>
        )
    }

在 React 中,父组件给子组件传递数据时,就是以上方式,通过给子组件设置 props 的方式,子组件取得 props 中的值即可完成数据传递。被传递数据的格式可以是任何 js 可识别的数据结构,上面demo是一个字符串。React 中,props 一般只作为父组件给子组件传递数据用,不要试图去修改自己的 props ,除非你想自找麻烦

props && state

上面提到了 props 不能被自身修改,如果组件内部自身的属性发生变化,该怎么办?—— React 为我们提供给了 state,先看一个demo:

class Hello extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.state = {
            // 显示当前时间
            now: Date.now()
        }
    }
    render() {
        return (
            <div>
                <p>hello world {this.state.now}</p>
            </div>
        )
    }
}

还有一点非常重要,React 会实时监听每个组件的 props 和 state 的值,一旦有变化,会立刻更新组件,将结果重新渲染到页面上,下面demo演示了state的变化,props也是一样的

class Hello extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.state = {
            // 显示当前时间
            now: Date.now()
        }
    }
    render() {
        return (
            <div>
                <p onClick={this.clickHandler.bind(this)}>hello world {this.state.now}</p>
            </div>
        )
    }
    clickHandler() {
        // 设置 state 的值的时候,一定要用 this.setState ,不能直接赋值修改
        this.setState({
            now: Date.now()
        })
    }
}
智能组件 & 木偶组件

这是用 React 做系统设计时的两个非常重要的概念。虽然在 React 中,所有的单位都叫做“组件”,但是通过以上例子,我们还是将它们分别放在了./app/containers./app/components两个文件夹中。为何要分开呢?

  • 智能组件 在日常开发中,我们也简称“页面”。为何说它“智能”,因为它只会做一些很聪明的事儿,脏活累活都不干。它只对数据负责,只需要获取了数据、定义好数据操作的相关函数,然后将这些数据、函数直接传递给具体实现的组件即可。
  • 木偶组件 这里“木偶”一词用的特别形象,它总是被人拿线牵着。它从智能组件(或页面)那里接受到数据、函数,然后就开始做一些展示工作,它的工作就是把拿到的数据展示给用户,函数操作开放给用户。至于数据内容是什么,函数操作是什么,它不关心。

以上两个如果不是理解的很深刻,待把课程学完再回头看一下这两句话,相信会理解的。

生命周期

React 详细的生命周期可参见这里,也可查阅本文档一开始的视频教程。这里我们重点介绍这个项目开发中常用的几个生命周期函数(hook),相信你在接下来的 React 开发中,也会常用这些。

以下声明周期,也没必要每个都写demo来解释,先简单了解一下,后面会根据实际的例子来解释,这样会更加易懂。

  • getInitialState

初始化组件 state 数据,但是在 es6 的语法中,我们可以使用以下书写方式代替

class Hello extends React.Component {
    constructor(props, context) {
        super(props, context);
        // 初始化组件 state 数据
        this.state = {
            now: Date.now()
        }
    }
}
  • render

最常用的hook,返回组件要渲染的模板。

  • comopentDidMount

组件第一次加载时渲染完成的事件,一般在此获取网络数据。实际开始项目开发时,会经常用到。

  • shouldComponentUpdate

主要用于性能优化,React 的性能优化也是一个很重要的话题,后面一并讲解。

  • componentDidUpdate

组件更新了之后触发的事件,一般用于清空并更新数据。实际开始项目开发时,会经常用到。

  • componentWillUnmount

组件在销毁之前触发的事件,一般用户存储一些特殊信息,以及清理setTimeout事件等。

点击查看更多内容
118人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消