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

最近,公司招了几个新同学,没有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

我们看一段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 (
        

{ this.remainingCharacters() }
); } }); React.render( , document.body );
jQuery

140
$("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 = 

你好

// 将声明的元素渲染到节点上 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 (
      
        {this.props.children}
      
    )
  }
}

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

MobX

预备知识

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

为什么需要MobX

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

最后

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

你可能感兴趣的:(写给新同学的基础入门文档)