写给新同学的基础入门文档

最近,公司招了几个新同学,没有React基础,就整理了一份入门文档,希望能帮助到他们。

前言

目标读者:刚接触React技术栈的新同学。
本文目标:希望读者能知道为什么要使用这些技术栈,我们要借助它解决什么问题。api用法相关请查阅文档。

脚手架

为什么需要脚手架

随着产品线的发展,我们会不断的有新项目需要生成。这时候我们就会有一些麻烦:

  1. 每一个新项目都需要重新配置各类文件;
  2. 不利于统一管理升级

脚手架做了什么

我们的脚手架借助了第三方命令行工具YEOMAN。我们借助它做的事情是:从目标地址拉取预先准备好的代码模版(如webpack.config.jssrc目录package.json...)到我们指定的目录。在执行它的命令后,会进入它的生命周期,依次做的事情是

  1. 状态初始化;
  2. 询问用户具体配置,比如脚手架类型、项目名称、作者等;
  3. 下载模版文件压缩包并解压到本地;
  4. 安装项目依赖并启动

我们只是继承它的一个基类,在它提供的生命周期里实现了这些事情。

脚手架项目地址
YEOMAN

安装依赖

npm install -g yarn

yarn config set registry https://registry.dingxiang-inc.com

yarn global add yo generator-ctu

如果长时间下载不了,换用淘宝源[https://registry.npm.taobao.org](https://registry.npm.taobao.org)试试。

脚手架初始化

mkdir test
cd test
yo ctu

如果一直卡在“正在下载项目模板”,可以直接到github直接下载解压。这样的话,你需要自己进入到项目中安装依赖、启动项目。

另:如果有兴趣,可以看下脚手架代码,看是在哪一步卡住,可以提交下代码优化下这个问题。

了解项目结构

如果初始化成功,你会看到如下项目结构[图片上传失败...(image-dac1c0-1592968805612)]
备注:

  1. data文件夹在真实项目中已经移入src内,放的是一些部署后可以被替换的资源,比如logo文件、配置文件
  2. .yo-rc可以不用管,脚手架生成后的产物。

可以进入readme.md查看src目录下的文件概述。

React

预备知识

JSX语法

为什么需要它

假设我们现在要实现一个功能,可以点击体验下

1. 输入框为空时,tweet按钮不可点

2. 输入框下方显示还可以输入的字符数量

3. 点击add photo按钮,剩余字符数量及add photo按钮状态发生改变(假定图片占用23个字符)
a548382d-c91e-4a2c-858b-af28ff9b86b0.gif
a548382d-c91e-4a2c-858b-af28ff9b86b0.gif

我们看一段jQuery和React代码的对比

React
var TweetBox = React.createClass({
  getInitialState: function() {
    return {
      text: "",
      photoAdded: false
    };
  },

  handleChange: function(event) {
    this.setState({ text: event.target.value });
  },

  togglePhoto: function(event) {
    this.setState(prevState => {
        return {
        photoAdded: !prevState.photoAdded
      }
    });
  },

  remainingCharacters: function() {
    const photoCharacterLength = 23
        const maxCharacterLength = 140

    if (this.state.photoAdded) {
      return maxCharacterLength - photoCharacterLength - this.state.text.length;
    }

    return maxCharacterLength - this.state.text.length;
  },

  render: function() {
    const { text, photoAdded } = this.state

    return (
        <div>
            <textarea onChange={this.handleChange}></textarea>
            <br/>
            <span>{ this.remainingCharacters() }</span>
            <button disabled={!text.length && !photoAdded}>
                Tweet
            </button>
            <button onClick={this.togglePhoto}>
                {photoAdded ? "✓ Photo Added" : "Add Photo" }
            </button>
        </div>
    );
  }
});
 
React.render(
  <TweetBox />,
  document.body
);
jQuery
<div class="well clearfix">
    <textarea></textarea>
    <br>
    <span>140</span>
    <button class="js-tweet-button" disabled>Tweet</button>
    <button class="js-add-photo-button">Add Photo</button>
</div>
$("textarea").on("input", function() {
    if ($(".js-add-photo-button").hasClass("is-on")) {
        // add phtot按钮已经点击,剩余输入文本数量再减23
        $("span").text(140 - 23 - $(this).val().length);
    } else {
        // 计算剩余文本数量
        $("span").text(140 - $(this).val().length);
    }


    if ($(this).val().length > 0 || $(".js-add-photo-button").hasClass("is-on")) {
            // 如果文本框里有内容或者add phtot按钮已经点击,tweet button设置为可点击状态
        $(".js-tweet-button").prop("disabled", false);
    } else {
            // tweet button设置为不可点击状态
        $(".js-tweet-button").prop("disabled", true);
    }
});


// 给添加照片的按钮绑定点击事件监听
$(".js-add-photo-button").on("click", function() {
    if ($(this).hasClass("is-on")) {
        $(this).removeClass("is-on").text("Add Photo");  // 切换add photo按钮显示状态


        $("span").text(140 - $("textarea").val().length);
        if ($("textarea").val().length === 0) {
            // 切换tweet按钮前需要先判断textarea当前状态
            $(".js-tweet-button").prop("disabled", true);
        }
    } else {
        $(this).addClass("is-on").text("✓ Photo Added");  // 切换add photo按钮显示状态


        $("span").text(140 - 23 - $("textarea").val().length);
        $(".js-tweet-button").prop("disabled", false);
    }
});
你会发现
  1. React中,state成为了事件和render()之间的过渡:每个事件不需要担心哪一部分的DOM发生变化,他们只需要设置state就可以了。相应的,当你写render()的时候,你也只需要担心现在的state是什么。
  2. jQuery没有中间的过渡层state,我们需要花费很大的精力来解决它们之间相互的联系,bug就经常会出现在这里。
  3. React中把各个UI组件独立出来,有利于提高UI组件的复用率同时降低各个UI组件的耦合。
  4. 新手在直接操作DOM时很难写出高效而又优雅的代码,从而使得前端代码变得越来越难以维护。

它是怎么运作的

当我们在代码里写jsx这个语法时,会被babel编译成浏览器可执行的代码。

比如

// jsx语法
const element = <h1 id='h1' className='h1'><span>你好</span></h1>

// 将声明的元素渲染到节点上
ReactDOM.render(element, document.getElementById('root'));

会被转成

const element = React.createElement(
    "h1",
     {
       id: "h1",
       className: "h1"
     }, // 节点/组件上的属性
     React.createElement("span", null, "你好") // 子元素
); 
ReactDOM.render(element, document.getElementById('root'));

执行React.createElement后,我们会得到一个用来描述这个节点的对象,比如元素是原生元素,或者是一个React组件,还是说只是一个单纯的文本,有没有子节点等等。
ReactDOM.render就会根据这个描述,解析出一个节点,如果有子节点就递归往下解析,最终解析出一棵DOM树,渲染到root节点里。
当我们通过调用this.stState改变state的时候,在this.stState这个函数内部,最终会调用组件的render函数,render函数会重新返回一个描述节点的对象。React会和之前的对象进行比较,来决定哪些组件需要重新计算渲染,来进行最细粒度的重绘。

前端路由

预备知识

url的#号
history对象
hashchange
popstate

什么是前端路由

客户端浏览器可以不依赖服务端,根据不同的URL渲染不同的视图界面。

为什么需要前端路由

Ajax出现之前,路由工作是由后端处理。在进行页面切换的时候,浏览器发送不同的url请求;服务器接收到浏览器的请求时,通过解析不同的url去拼接需要的html或者模板,然后将结果返回给浏览器端进行渲染。

服务端渲染的优势:

  1. 安全性更高,更严格得控制页面的展现,如下单支付流程
  2. 有利于SEO
  3. 首屏渲染快

服务端渲染的优势:

  1. 服务器的计算压力,消耗服务器性能
  2. 不容易维护,如果不使用node中间层,前后端分工不明确,前后端可能同时在一个项目中开发
  3. 每一次切换页面都需要reload页面,用户体验较差

前端路由渲染的目标

  1. 在页面不刷新的前提下实现url变化
  2. 捕捉到url的变化,以便执行页面替换逻辑

它是怎么运作的

hash(IE 8)

打开控制台,执行下面代码

window.addEventListener('hashchange', function() {
  console.log('The hash has changed!')
}, false);
window.location.hash = 'testhash'

你会发现控制台执行了回调函数,打印了 The hash has changed! ,在url上也能看到 #testhash ,在url上直接改变 # 后面的内容,同样会执行回调函数。

history(IE 10)
window.onpushstate = function () {
     console.log('The hash has changed!')
}
(function (history) {
    var pushState = history.pushState;
    history.pushState = function (state,title,pathname) {
        if (typeof window.onpushstate == "function") {
            window.onpushstate(state,pathname);
        }
        return pushState.apply(history, arguments);
    };
})(window.history);

因为pushStatereplaceState不会触发onpopstate事件事件,所以可以采用 aop 的方法进行监听。现在,我们就可以通过调用 pushState 的方法来改变路径,同时我们也能监听到。

let stateObj = {
    foo: "bar",
};

history.pushState(stateObj, "page 2", "/bar.html")

我以hashRouter举例

import RouterContext from './RouterContext'
import React, { Component } from 'react'
const location = window.location

export default class Router extends Component {
  state = {
    location: {
      pathname: location.hash.slice(1),
      state: null
    },
    history: {
      push: (to) => {
        if (typeof to === 'object') {
          window.location.hash = to.pathname
          this.locationState = to.state
        } else {
          window.location.hash = to
        }
      }
    }
  }
  locationState = undefined
  componentDidMount () {
    window.addEventListener('hashchange', (HashChangeEvent) => {
      this.setState({
        location: {
          ...this.state.location,
          pathname: location.hash.slice(1),
          state: this.locationState
        }
      })
    })
  }
  render() {
    return (
      <RouterContext.Provider value={this.state} >
        {this.props.children}
      </RouterContext.Provider>
    )
  }
}

其他组件如 SwitchRoute 等内部逻辑都比较好理解,感兴趣可以自己继续探究。

MobX

预备知识

建议通读MobX文档,文档中推荐的入门文章,这里是译文

为什么需要MobX

四张图解释了为什么需要Redux,MobX同理。

最后

现在,你已经大致了解现有技术栈,可以在脚手架生成的项目中仿造用户管理界面仿造写一个具有增删改查的功能的页面,比如系统中的产品管理。遇到问题先查阅文档或谷歌,还有不清楚及时问师兄。你的目标是熟悉一个简单页面的搭建,加油!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345