【React主场】VUE vs React 高手过招!

13,932 阅读16分钟

人格尊严重要部分:具有选择权!

只会vue哪儿能行?React我也要!

学习react,最好的方式当然是:逐行阅读官方文档😆

由于笔者阅读障碍症晚期,所以将官方文档上的内容,进行简化拆分,并与vue进行对比。

希望最终能帮助与笔者一样患有阅读障碍的江湖儿女💖




1.JSX


独孤九剑-应用篇

React 不强制要求使用 JSX,但是JSX会使你的代码更具有阅读性。

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;//这就是JSX; {} 内部可以嵌入js表达式

ReactDOM.render(
  element,
  document.getElementById('root') //将jsx内的dom绑定到#root div上
);


无招胜有招-原理篇

react有一个all in js 的概念,所有的一切都在js文件中实现,比如dom标签。

vue则是 html+js+css 三剑客,这也是vue学习成本低的原因之一。

JSX更像是javascript里的: html+=“<h1>hello world</h1>”

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

所以,JSX的本质,是一种语法糖,方便阅读,省掉模板语法的学习成本。





2.元素渲染(核心)


独孤九剑-应用篇

React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。

更新 UI 唯一的方式是创建一个全新的元素,并将其传入

ReactDOM.render() //渲染UI的函数
function tick() {
  const element = (   //----记得加括号---- 不然可能会出现自动加 ; 的现象
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root')); //每隔1秒,重新创建UI
}
setInterval(tick, 1000);


无招胜有招-原理篇

ReactDOM.render() 最终会将 JSX 代码转换成转换为真实的DOM元素。

但DOM的疯狂渲染,会导致浏览器资源被大量占用,所以就出现了:

diff算法,算出与上次比较,被修改的内容,只更新替换被修改的DOM元素。


重剑剑法-比较篇

VUE中,响应式原理实现了对data()内数据对象的监听,触发变更;

而不像React需要每次都重新调用render渲染。

此处vue赢得一筹👍






3.组件 & Props


独孤九剑-应用篇

定义组件最简单的方式就是编写 JavaScript 函数

调用这样一个函数,自然也就是调用组件了🤣

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}


比较推荐的做法,是ES6的 class 定义组件,全套服务,自带render

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}


也要注意,除了原生的 h1 div 等标签,还有自定义组件标签

function Welcome(props) {    // 学过vue的同学,肯定不会对props陌生
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

warning!!!组件名称必须以大写字母开头。 小写字母将会被视为 原生标签


秘技-多重组件之术!

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />  //组件套组件,何其美哉?
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

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


重剑剑法-比较篇

相比react组件的随意,vue的组件无疑就沉稳的多,每一个组件都有自己完整的生命周期,props传参之类的,也更加清晰。

而上述的 react 组件,似乎少了点感觉






4.State & 生命周期(真正的组件封装)


独孤九剑-总决式

让react的组件更像vue(实际上反了,是vue模仿并优化了react的组件风格)

这里就很难简单几句话把事情说清楚了😔

但是我还是想多说两句......

如果你觉得  funtion 函数式声明太儿戏了,那就用Class吧,显得逼格高一些😎

class Clock extends React.Component {  
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

高逼格要点:

  1. 声明ES6 class 并继承 React.Component
  2. class 里 编写一个 render() 函数(这个函数将被自动调用)
  3. 在render内使用 this。
  4. 拿出一支烟,然后在旁边前端小姐姐的注视下愣住一秒,再歉意一笑,把烟放回烟盒。


我们还是借助官网的案例 生成一个 定时器 组件:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

首先讲一下ES6基础, Class也是ES6提供的语法糖,constructor 是class的构造函数,当class被new实例化时,会自动调用constructor,  super 代表了父类(React.Component)的构造函数(constructor)。

也就是说:当Clock被实例化的时候,会去调用内部的constructor,继而又通过super去执行React.Component的constructor。🎈


生命周期:🌱

componentDidMount(),是生命周期函数,组件在初始化时挂载,接近于vue的mounted()

componentWillUnmount(),组件被删除/销毁时触发,与vue的destroyed()类似。

ps:当你离开页面时,如果不销毁定时器,则定时器会一直存在,占用内存。
 componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }


无招胜有招-原理篇

state 是 react 的一个关键字,应当是对内的局部的 。与之对应的有props。

Class 组件应该始终使用 props 参数来调用父类的构造函数。

state 修改时,要调用 setState() 来修改。

因为setState能 重新渲染组件,已经很接近于vue响应式的概念了。

与之接近的,vue也有$set的概念,但是二者意义不同:

  1. react的setState()是为了触发页面更新,省掉你的render步骤(或者是隐式调用)
  2. vue的$set()是为了将你之前在data()内未曾绑定的,绑定为响应式对象。


所以,我们这里也可以简单的理解为:

constructor 内的 state 和 vue中的data() 目的一致。


出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用
因为 this.propsthis.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态


state可以作为props向他的子组件传递,但是这个过程是向下传递的,父级不会关注你的子级组件,所以,这也就是为什么说 react 是单向数据流——数据是向下流动的







5.事件处理


独孤九剑-应用篇

<button onClick={activateLasers}>
  Activate Lasers
</button>

熟悉的 {} ,react写法 和 vanillajs 一样,onclick等事件原封不动,事件内部的事件对象也与 vanillajs 毫无差异。

但是要注意:onClick 是小驼峰🐪🐪🐪 小驼峰噢🐪🐪🐪

ps:vanillajs 译为 香草javascript,是世界上最轻量级的javascript框架。😘
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }
在这里,e 是一个合成事件。React 根据 W3C 规范来定义这些合成事件,所以你不需要担心跨浏览器的兼容性问题。


以class的方式定义事件:

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

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

事件回调实际上是绑定在 constructor内的,通过function的内置函数bind,将this指向到class Toggle 本身 

这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。

ps:React的事件,建议阅读 官方文档,反复看两遍。


重剑剑法-比较篇

  1. React 要做额外的 this绑定操作
  2. VUE直接默认this指向


无招胜有招-原理篇

我想很多同学会疑惑:为什么React和VUE,都在this指向上花费这么多功夫。

因为组件化的思想,是希望所有参数都来自于组件内部,将这些参数私有化,形成一个独立的内部循环,这时,就需要this来统领一切。


还有啊:是小驼峰🐪🐪🐪 小驼峰噢🐪🐪🐪






6.条件渲染


独孤九剑-应用篇

说到条件渲染,很容易就想起 VUE 里的 v-if v-show 等指令了。

而React则是更直接些了:

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

毕竟是 All in JS,想来VUE的v-if,也是为了模拟这种操作吧😆


与运算符 &&

   <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>

其实这段JSX代码就是很简单的利用了 vanillajs 的 && 运算符:

当 && 前面的内容 返回为true,则继续执行&&后面的内容。

是的,他能实现你想象中的效果,三目运算符(boolean?a:b)同理😋


无招胜有招-原理篇

so,所谓的条件渲染,就是 if else 这样的 js 判断罢了。






7.列表 & Key


独孤九剑-应用篇

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
       {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
  1. 同理,参照vue 的 v-for
  2. all in js 的 React 借助 数组的map() 遍历函数 实现了它的v-for。
  3. 唯一属性key的目的,是为了帮助React识别哪个子元素发生了改变
  4. 如果里面的li是一个组件的话,记得将key绑定在组件被引用的地方


无招胜有招-原理篇

React的列表,也是借用了 vanillajs 的循环来实现的,而不是React本身做了什么。顶多是JSX对循环做了支持。

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
      )}
    </ul>
  );
}








8.表单(女人听了流泪...)


独孤九剑-应用篇

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

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

  handleSubmit(event) {
    alert('提交的名字: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
         <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">葡萄柚</option>
            <option value="lime">酸橙</option>
            <option value="coconut">椰子</option>
          </select>
         <input type="submit" value="提交" />      </form>
    );
  }
}

React 希望你将 state 作为表单绑定的 “唯一数据源”。并称之为受控组件

你需要在构造器(constructor)中绑定 表单数据&交互事件 ,在类中也需要对事件进行定义。


重剑剑法-比较篇

  1. React 将其value绑定为state的子属性,通过setState()修改
  2. VUE实现了一个v-model的语法糖,用来完成同等操作,无疑简单的多。
  3. React还有原生体验的 target.name 的方式,通过name批量处理
        <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
   
        <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
 handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }


如果你觉得React表单构造起来很难受,那你可以试试第三方支援 formik

formikBuild forms in React, without the tears.
在React中构建表单,不再流泪。







9.状态提升(父子级传参)


独孤九剑-应用篇

通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。

实际上,状态提升,可以对照为 VUE 中的:父子级传参、兄弟节点传参


父级:

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.handle_A_Change= this.handle_A_Change.bind(this);    this.handle_B_Change= this.handle_B_Change.bind(this);    this.state = {temperature: '', scale: 'c'};
  }

  handle_A_Change(temperature) {
    this.setState({scale: 'c', temperature});
  }

  handle_B_Change(temperature) {
    this.setState({scale: 'f', temperature});
  }

  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

    return (
      <div>

        <children 
          scale="c"
          temperature={celsius}
          on_my_Change={this.handle_A_Change} />
        <children 
          scale="f"
          temperature={fahrenheit}
          on_my_Change={this.handle_B_Change} />        <BoilingVerdict
          celsius={parseFloat(celsius)} />
      </div>
    );
  }
}


子级:

class children extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.on_my_Change(e.target.value);  }

  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}


无招胜有招-原理篇

  1. 将子组件绑定的 state,修改为绑定父级传来的 props
  2. 从子组件 触发 props 中的 父组件 函数事件
  3. 父级通过函数事件 对 自身 的state 执行 setState()
  4. setState()的执行会调用render() 从新渲染子级。

简单的说就是:

子级调用了父级的方法,修改父级的state,父级 再重新渲染 子级


重剑剑法-比较篇

  1. vue的跨组件传参,是通过$emit('eventName') 调用父级函数
  2. 但是vue中的子组件,依然是保留了自身的data()或者props()属性的。
  3. 而React直接就将属性从子级中剔除,通过父级来中转。
  4. 此处再次体现了React 单向数据流 思想
  5. 从这点看,vue似乎将 组件化思想 执行的更完美。

(写到这里,笔者觉得有些不对劲,子组件的属性,或许有其他方式子组件保留副本)







10.组合 vs 继承

React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}



重剑剑法-比较篇

  1. React通过特殊的 prop.children + JSX 的形式来实现父子级嵌套。
  2. vue中的组件嵌套是通过 <slot>标签。
  3. 两者差异不大。


独孤九剑-应用篇

function Contacts() {
  return <div className="bg-blue width100 height200" />;
}

function Chat() {
  return <div className="bg-red width100 height200" />;
}

function SplitPane(props) {
  return (
    <div className="width200 height200">
      <div className="inline-block float-left">
        {props.left}
      </div>
      <div className="inline-block float-right">        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

如果打算开启多个“插槽”,则可以自定义props.children的名称:left、right等



但是官方也说了,这个东西并不能被局限为插槽

这种方法可能使你想起别的库中“槽”(slot)的概念,但在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递。
function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />
  );
}

此处实现了一个dialog窗体。


重剑剑法-比较篇

  1. React可以通过 props 传递任何,一个对象,一个JSX,甚至一个组件
  2. vue中的props则限制更多,甚至设定了接受类型 String、boolean、Array等
  3. 似乎VUE在简化编码&简化思考&有限选择的道路上越走越远
  4. 而React则在此处彻底体现了强大的灵活性,越玩越high~


无招胜有招

Facebook,我们在成百上千个组件中使用 React。我们并没有发现需要使用继承来构建组件层次的情况。

Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。







React 哲学 - 剑意

React 最棒的部分之一是引导我们思考如何构建一个应用


组件层级

  1. 将组件当作一种函数或者是对象来考虑,根据单一功能原则来判定组件的范围
  2. 如果你的模型设计得恰当,UI(或者说组件结构)便会与数据模型一一对应
  3. 因为 UI 和数据模型都会倾向于遵守相同的信息结构



静态版本

  1. 渲染 UI 添加交互这两个过程分开。
  2. 编写一个应用的静态版本时,往往要编写大量代码,而不需要考虑太多交互细节
  3. 添加交互功能时则要考虑大量细节,而不需要编写太多代码。


静态版本-渲染UI

  1. 创建一些会重用其他组件的组件,然后通过props传入所需的数据,而不应该使用 state 构建静态版本
  2. state 代表了随时间会产生变化的数据,应当仅在实现交互时使用。
  3. 自上而下或者自下而上构建应用。(面向过程或者面向对象),当你的应用比较简单时,使用自上而下的方式更方便,反之则自下而上的优先编写基础组件



通过State完成交互

state具有触发基础数据模型改变的能力(setState自动调用Render)

  1. 确认state已被分割到最小(前提是完整)
  2. 对应的概念是:
    Don’t Repeat Yourself (不要重复你自己)

  3. 保留应用所需的可变 state 的最小集合其他数据均由它们计算产生



确定 state 放置的位置

哪个组件能够 改变&拥有 这些 state? 父组件 or 子组件

  1. React 是单向数据流,顺着组件层级从上往下传递
  2. 找到根据这个 state 进行渲染的所有组件
  3. 找到它们的共同父级组件,将state放置于此
  4. 如果找不到合适的,则直接创建一个父级组件



为什么React选择了单向数据流?

React哲学认为:显式声明单向数据流,更有助于人们理解程序的运作方式
比起写,代码更多地是给人看的。我们一起构建的这个模块化示例应用的代码就很易于阅读。


更详细内容,请见官方文档 React哲学






独孤九剑(React) vs 重剑剑法(VUE)

平心而论,笔者在本次系统的学习React之前,一直想知道,为什么大厂都更倾向于使用React而非VUE,这次文章编写后,算是有了些自己的考量了。


React:

更有助于人们理解程序的运作方式。——出自 React官网-React哲学

Vue:

更好地专注于应用本身。——出自 VUE官网-对比其他框架


大厂选择React肯定是出于很多方面的考量,我想其中就少不了“React哲学”,毕竟低级码农对大厂来说是没有价值的,设定一个React门槛也是很有价值的。

而Vue也确确实实就如作者所说,照顾到了小型企业的开发成本,降低了前端开发的门槛,处处都进行了简化和优化。






最后

如果你是一名初入行业的前端开发者,我强烈建议自行学习React,并从实际应用中学习其中的编码思想

目前各种培训机构,都只是在培训技能,进入公司后,忙于工作,也不一定有前辈高人愿意指点,React则是一本剑谱,一本秘籍,从中领悟出剑意,才能成为一个真正的武林高手