人格尊严的重要部分:具有选择权!
只会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>
);
}
}
高逼格要点:
- 声明ES6 class 并继承 React.Component
- class 里 编写一个 render() 函数(这个函数将被自动调用)
- 在render内使用 this。
- 拿出一支烟,然后在旁边前端小姐姐的注视下愣住一秒,再歉意一笑,把烟放回烟盒。
我们还是借助官网的案例 生成一个 定时器 组件:
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的概念,但是二者意义不同:
- react的setState()是为了触发页面更新,省掉你的render步骤(或者是隐式调用)
- vue的$set()是为了将你之前在data()内未曾绑定的,绑定为响应式对象。
所以,我们这里也可以简单的理解为:
constructor 内的 state 和 vue中的data() 目的一致。
出于性能考虑,React 可能会把多个setState()
调用合并成一个调用。
因为this.props
和this.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的事件,建议阅读 官方文档,反复看两遍。
重剑剑法-比较篇
- React 要做额外的 this绑定操作
- 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')
);
- 同理,参照vue 的 v-for
- all in js 的 React 借助 数组的map() 遍历函数 实现了它的v-for。
- 唯一属性key的目的,是为了帮助React识别,哪个子元素发生了改变。
- 如果里面的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)中绑定 表单数据&交互事件 ,在类中也需要对事件进行定义。
重剑剑法-比较篇
- React 将其value绑定为state的子属性,通过setState()修改。
- VUE实现了一个v-model的语法糖,用来完成同等操作,无疑简单的多。
- 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
formik:Build 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>
);
}
}
无招胜有招-原理篇
- 将子组件绑定的 state,修改为绑定父级传来的 props
- 从子组件 触发 props 中的 父组件 函数事件
- 父级通过函数事件 对 自身 的state 执行 setState()
- setState()的执行会调用render() 从新渲染子级。
简单的说就是:
子级调用了父级的方法,修改父级的state,父级 再重新渲染 子级
重剑剑法-比较篇
- vue的跨组件传参,是通过$emit('eventName') 调用父级函数
- 但是vue中的子组件,依然是保留了自身的data()或者props()属性的。
- 而React直接就将属性从子级中剔除,通过父级来中转。
- 此处再次体现了React 单向数据流 思想
- 从这点看,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>
);
}
重剑剑法-比较篇
- React通过特殊的 prop.children + JSX 的形式来实现父子级嵌套。
- vue中的组件嵌套是通过 <slot>标签。
- 两者差异不大。
独孤九剑-应用篇
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窗体。
重剑剑法-比较篇
- React可以通过 props 传递任何,一个对象,一个JSX,甚至一个组件
- vue中的props则限制更多,甚至设定了接受类型 String、boolean、Array等
- 似乎VUE在简化编码&简化思考&有限选择的道路上越走越远
- 而React则在此处彻底体现了强大的灵活性,越玩越high~
无招胜有招
在 Facebook,我们在成百上千个组件中使用 React。我们并没有发现需要使用继承来构建组件层次的情况。
Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。
React 哲学 - 剑意
React 最棒的部分之一是引导我们思考如何构建一个应用。
组件层级
- 将组件当作一种函数或者是对象来考虑,根据单一功能原则来判定组件的范围。
- 如果你的模型设计得恰当,UI(或者说组件结构)便会与数据模型一一对应。
- 因为 UI 和数据模型都会倾向于遵守相同的信息结构。
静态版本
- 将渲染 UI 和添加交互这两个过程分开。
- 编写一个应用的静态版本时,往往要编写大量代码,而不需要考虑太多交互细节。
- 添加交互功能时则要考虑大量细节,而不需要编写太多代码。
静态版本-渲染UI
- 创建一些会重用其他组件的组件,然后通过props传入所需的数据,而不应该使用 state 构建静态版本。
- state 代表了随时间会产生变化的数据,应当仅在实现交互时使用。
- 自上而下或者自下而上构建应用。(面向过程或者面向对象),当你的应用比较简单时,使用自上而下的方式更方便,反之则自下而上的优先编写基础组件。
通过State完成交互
state具有触发基础数据模型改变的能力(setState自动调用Render)
- 确认state已被分割到最小(前提是完整)
- 对应的概念是:Don’t Repeat Yourself (不要重复你自己)
- 保留应用所需的可变 state 的最小集合,其他数据均由它们计算产生
确定 state 放置的位置
哪个组件能够 改变&拥有 这些 state? 父组件 or 子组件 ?
- React 是单向数据流,顺着组件层级从上往下传递
- 找到根据这个 state 进行渲染的所有组件
- 找到它们的共同父级组件,将state放置于此
- 如果找不到合适的,则直接创建一个父级组件
为什么React选择了单向数据流?
React哲学认为:显式声明单向数据流,更有助于人们理解程序的运作方式
比起写,代码更多地是给人看的。我们一起构建的这个模块化示例应用的代码就很易于阅读。
更详细内容,请见官方文档 React哲学
独孤九剑(React) vs 重剑剑法(VUE)
平心而论,笔者在本次系统的学习React之前,一直想知道,为什么大厂都更倾向于使用React而非VUE,这次文章编写后,算是有了些自己的考量了。
React:
更有助于人们理解程序的运作方式。——出自 React官网-React哲学
Vue:
更好地专注于应用本身。——出自 VUE官网-对比其他框架
大厂选择React肯定是出于很多方面的考量,我想其中就少不了“React哲学”,毕竟低级码农对大厂来说是没有价值的,设定一个React门槛也是很有价值的。
而Vue也确确实实就如作者所说,照顾到了小型企业的开发成本,降低了前端开发的门槛,处处都进行了简化和优化。
最后
如果你是一名初入行业的前端开发者,我强烈建议自行学习React,并从实际应用中学习其中的编码思想。
目前各种培训机构,都只是在培训技能,进入公司后,忙于工作,也不一定有前辈高人愿意指点,React则是一本剑谱,一本秘籍,从中领悟出剑意,才能成为一个真正的武林高手