React 从青铜到王者系列教程之倔强青铜篇

阅读 1363
收藏 141
2018-02-27
原文链接:zhuanlan.zhihu.com

前端大陆乃是技术界近年来新兴起的板块,随着人们对网站交互和性能越来越高, 前往前端大陆修炼 Javascript 的召唤师如过江之鲫,数不胜数,前端奇人异士之多,故修炼之法林林总总,俱不相同, Web 前端的未来尚无定论,内部却已起了门户之见, 幸而前端圈核心门派正道大昌,人气鼎盛,其中尤以 React、Vue、Angular 为三大支柱,是为领袖,今天的故事,便是从 React 峡谷开始的

React 峡谷的每个前端召唤师,根据对 React 技术栈 和前端的理解,分别是青铜,白银,黄金,铂金,钻石,星耀和王者段位,也对应这这个系列教程的七个模块

  1. 倔强青铜
  • 初入峡谷的初始段位,默认召唤师已经有了ES6nodejs的基础
  • 使用create-react-app建立React开发环境
  • 学习React世界里的基本玩法,例如组件化,JSX,事件监听,内部state,组件的props、生命周期函数等
  • 这篇文章主要介绍React青铜升白银需要的基础知识,看完你就白银啦
  1. 秩序白银
  • 到了白银段位,基本都是有了基本的操作,不会出现呆萌的站在地方塔下被打死的情况了
  • 我们需要买一个皮肤来提升页面美观并且多加练习
  • 学习使用蚂蚁金服ant-design的使用
  1. 荣耀黄金
  • 到了这个阶段,召唤师对React有了基本的认识,想进一步的提升段位,我们需要提高自己的大局观
  • 学习使用React-Router4来让我们有多面作战能力
  • 学会使用BrowserRouterRouterLink等组件
  • 学会使用Redux和队友配合,修炼大局观
  • 了解单项数据流开发模式和Redux的各种概念,如dispatch,action,reducers
  • 使用react-redux更好的和Redux配合有大局观意识,上铂金也是很 easy 了
  1. 尊贵铂金
  • 很多召唤师卡在铂金上不去,因为铂金想上钻石,需要了解更多的细节和原理
  • React原理剖析
  • 对自己技能的原理有深刻的了解,上钻石必备
  1. 永恒钻石
  • 钻石段位开始了征兆模式,召唤师的技能池要足够深才能更进一步,对自己擅长技能的理解也要更深刻
  • Redux中间件机制,实现自己的中间件
  • 常见的React 性能优化方式
  • 服务端渲染
  • Redux之外其他的数据解决方案如mobxdva
  1. 至尊星耀
  • 这个段位已经是独当一面的强者了,目标仅限于 React 这个库很难更近一层,需要了解更底层的原理
  • Redux原理剖析+实现自己的Redux
  • React-Router+实现自己的React-Router
  • Webppack工作机制和原理剖析
  • Babel工作机制和原理剖析
  1. 最强王者
  • 达到最强王者已经是顶尖召唤师,在整个 React 峡谷都是鼎鼎大名的存在,听说上面还有传说中的荣耀王者的段位,我这辈子是达不到了,也没法教你们了,囧
  • 这个阶段,我只推荐《程序员健康指南》一本书,保持身心健康,成为荣耀王者是早晚的事

下面开始我们的正文,倔强青铜篇目录

  1. 环境搭建
  2. 第一个组件
  3. 组件嵌套和属性传递
  4. 状态处理
  5. 生命周期

环境搭建

默认大家已经有 node 环境了,先安装脚手架

npm install create-react-app -g

然后建立项目,并启动

create-react-app bronze
cd bronze
npm start

看到下面的图,意味着第一个React应用已经启动起来了

我们打开 src 目录

src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── registerServiceWorker.js

index.js是入口文件,核心内容就是分别引入ReactReactDom,然后渲染了一个组件App#root这个元素上

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

ReactDOM.render(<App />,document.getElementById('root'));

然后重点看下App.js是我们组件的具体内容

import React, { Component } from 'react'

class App extends Component {
  render() {
    return (
      <div className="App">
        <p>
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;

这个基本上是最简单的React组件了,自己实现组件也是分这么几个步骤

  1. import React
  2. 新建一个类,继承React.ComponentReact里每个组件都可以写成一个类
  3. 类的render函数返回值,就是显示在页面的内容
  4. render里返回的是东西有点诡异,表面上是html其实Babel会把JSX转成React.createElememt来执行
  5. 由于JSX本质就是 js,class是 js 的关键字,所以要用className代替
  6. 如果想在JSX里渲染变量,使用{}包裹即可

现在[Babel官网]](babeljs.io/repl/),看下JSX编译后的代码,再划下重点,所谓的JSX,其实就是js 对象,再用ReactDom.render方法,渲染成dom

// jsx
<div className="App">
  <p>hello react</p>
</div>

// 转换后
React.createElement(
  "div",
  { className: "App" },
  React.createElement(
    "p",
    null,
    "hello react"
  )
)

第一个组件

我们实现自己的第一个组件,修改App.js

import React, { Component } from 'react'

class App extends Component {
  render() {
    const level='最强王者'
    return (
      <div>
        <h2>我们的目标是{level}</h2>
      </div>
    )
  }
}

export default App

由于JSX本质上其实就是 js,所以我们可以在{}里面使用 js 的表达式等功能,比如三元、函数、变量等等,还可以把数组映射为列表,我们把代码修改为

import React, { Component } from 'react'

class App extends Component {
  render() {
    const level='最强王者'
    const isKing = true

    const title = isKing
                    ? <p>早睡早起,理性游戏</p>
                    : <h2>我们的目标是{level}</h2>

     const wordList = ['俺老孙来也','有妖气','取经之路,就在脚下']
    return (
      <div>
        {title}
        {isKing ? <p>我就是王者</p>: null}
        <ul>
          {wordList.map(v=><li key={v}>{v}</li>)}
        </ul>
      </div>
    )
  }
}

export default App

这里要稍微注意一点,就是render里,如果return多个元素,必须有一个父元素包裹,否则会有个报错

我们在return 之外把JSX复制给变量,JSX里也可以在{}内部使用三元表达式,大家可以修改isKingfalse试一试

然后就是渲染列表,我们使用map函数直接映射为JSX的数组,记得列表里每个元素都有一个key属性,关于它的作用,我们讲虚拟dom 的时候会介绍

组件嵌套和属性传递

如果我们继续设计我们的应用,现在再设计一个Tank组件,可以直接放在App里使用,并且可以传递一个属性,在组件内部,使用this.props.key获取

import React, { Component } from 'react'

class App extends Component {
  render() {
    const level='最强王者'
    const isKing = true

    const title = isKing
                    ? <p>早睡早起,理性游戏</p>
                    : <h2>我们的目标是{level}</h2>
    return (
      <div>
        {title}
        {isKing ? <p>我就是王者</p>: null}
        <Tank name='程咬金'></Tank>
      </div>
    )
  }
}
class Tank extends Component {
  render() {
    return (
        <div>
          <h3>{this.props.name}是一个坦克</h3>
        </div>
    )
  }
}

export default App

如果我们的组件只有一个render方法,还可以写成一个函数,props 是函数的参数,我们称呼这种组件为无状态组件,这种组件的特点,就是返回只和 props 有关,复用性高

function Tank(props){
  return (
    <div>
      <h3>{props.name}是一个坦克</h3>
    </div>
  )
}

这样我们就可以把应用分成多个组件,然后用拼积木的形式拼接应用,但是现在的组件都没法变化,下一步,我们学习 React 的状态管理,也就是 state

React 状态管理和事件监听

我们通过在构造函数constructor里初始 state,其实就是一个普通的 js 对象,然后可以调用 this.setState函数修改 state,每次 setState,都会重新渲染组件 组件里可以使用 onClick来绑定一个函数,可以监听用户的事件,话不多说看代码

class App extends Component {
  constructor(props){
    super(props)
    this.state = {
      isKing:true
    }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick(){
    this.setState({
      isKing: !this.state.isKing
    })
  }
  render() {
    const level='最强王者'

    const title = this.state.isKing
                    ? <p>早睡早起,理性游戏</p>
                    : <h2>我们的目标是{level}</h2>
    return (
      <div>
        <button onClick={this.handleClick}>点我</button>
        {title}
        {this.state.isKing ? <p>我就是王者</p>: null}
        <Tank name='程咬金'></Tank>
      </div>
    )
  }
}

我们需要关注的点,一个是constructor,我们称之为构造函数,组件初始化状态放在这里,设置了isKingtrue,然后button元素上的onClick的时候,执行handleClick,在handleClick内部,调用this.setState来修改isKing

constructor函数里的bind是强行把handleClickthis绑定在组件上,否则onClick的时候会获取this引用出错,解决这个问题还有其他的形式,可以不用写bind这一行

import React, { Component } from 'react'

class App extends Component {
  constructor(props){
    super(props)
    this.state = {
      isKing:true
    }
    this.handleClick = this.handleClick.bind(this)
  }
  // 在constructor里手动 bind
  handleClick(){

    this.setState({
      isKing: !this.state.isKing
    })
  }
  // 绑定的时候传递箭头函数
  handleClick1(){
    this.handleClick()
  }
  // 定义的时候是剪头函数
  handleClick2 = ()=>{
    this.handleClick()
  }
  // onClick 的时候直接绑定
  handleClick3(){
    this.handleClick()
  }
  render() {
    const level='最强王者'

    const title = this.state.isKing
                    ? <p>早睡早起,理性游戏</p>
                    : <h2>我们的目标是{level}</h2>
    return (
      <div>
        <button onClick={this.handleClick}>点我</button>
        <button onClick={()=>this.handleClick1()}>点我1</button>
        <button onClick={this.handleClick2}>点我2</button>
        <button onClick={this.handleClick3.bind(this)}>点我3</button>
        {title}
        {this.state.isKing ? <p>我就是王者</p>: null}
        <Tank name='程咬金'></Tank>
      </div>
    )
  }
}

生命周期

最后要介绍的,就是React组件的生命周期,每个组件在不同的时期,会有不同的钩子函数执行,比如组件加载完毕后,会执行componentDidMount钩子函数

class App extends Component{
  componentDidMount(){
    console.log('组件渲染完毕')
  }
  render(){
    console.log('组件正在渲染')
    return <h2>倔强青铜</h2>
  }
}

// 组件正在渲染
// 组件渲染完毕

React在不同的阶段,会执行不同的函数,我从网上找了个图,很清晰的说明各个生命周期函数执行的时机,

class App extends Component {
  constructor(props){
    super(props)
    this.state = {
      isKing:true
    }
    this.handleClick = this.handleClick.bind(this)
    console.log('constructor App 的构造函数,初始化先执行')
  }
  handleClick(){
    this.setState({
      isKing: !this.state.isKing
    })
  }
  componentWillMount(){
    console.log('componentWillMount,组件 App 准备渲染')
  }
  componentDidMount(){
    console.log('componentDidMount,组件 App 渲染完毕')
  }
  shouldComponentUpdate(){
    console.log('shouldComponentUpdate,判断 App 组件是否应该渲染, 默认返回 true')
    return true
  }
  componentWillUpdate(){
    console.log('componentWillUpdate,组件 App 准备更新了')
  }
  componentDidUpdate(){
    console.log('componentDidUpdate, 组件 App 更新完毕了')
  }
  render() {
    const level='最强王者'

    const title = this.state.isKing
                    ? <p>早睡早起,理性游戏</p>
                    : <h2>我们的目标是{level}</h2>
    const wordList = ['俺老孙来也','有妖气','取经之路,就在脚下']
    console.log('组件 App 正在渲染')
    return (
      <div>
        <button onClick={this.handleClick}>点我</button>
        {title}
        {this.state.isKing ? <p>我就是王者</p>: null}

        <ul>
          {wordList.map(v=><li key={v}>{v}</li>)}
        </ul>
        <Tank name='程咬金'></Tank>
      </div>
    )
  }
}

// 首次加载打印
constructor App 的构造函数,初始化先执行
componentWillMount,组件 App 准备渲染
组件 App 正在渲染
componentDidMount,组件 App 渲染完毕

// 点击按钮修改状态时打印
shouldComponentUpdate,判断 App 组件是否应该渲染, 默认返回 true
componentWillUpdate,组件 App 准备更新了
组件 App 正在渲染
componentDidUpdate, 组件 App 更新完毕了

除了上面介绍的,还有需要注意的

  1. React16新增的componentDidCatch生命周期,用来处理报错
  2. shouldComponentUpdate返回false,那么组件就不会渲染
  3. 如果是子组件,还有个componentWillReceiveProps
  4. 组件卸载有componentWillUnmount,用来做资源的清理
  5. 合理利用生命周期,在不同的阶段做不同的事情 如果你能看到这里,那真的对React是真爱,恭喜你,你已经是秩序白银啦,领取徽章

今天的代码都在github

下期预告:秩序白银篇-- 使用ant-designUI库,有问题留言,我们还可以开黑,一起上王者

评论
说说你的看法
相关收藏集