前言—react与vue对比
相同点
1、都使用虚拟DOM来提高渲染效率。
2、都支持服务端渲染(SSR)。
3、都有响应式编程的概念,能够自动更新DOM。
4、都可以通过组合或继承的方式创建组件。
5、都有支持的社区和丰富的资源。
不同点
1、vue : mvvm
模式,依赖追踪模式
2、react单向数据流,ui = render(data)
props 只读属性,父向子流动,使用state、setState更新数据
3、运行时和编译时差别
- vue:
(1)编译时 对template
或jsx
解析,对使用受限制的语法v-if/v-for编译时转换,vue3可以做到按需编译
(2)运行时diff
优化 - react:
侧重运行时jsx
编译成js createElement
,此时做diff
比较(fiber框架针对diff掉帧做的优化)
4、数据是否可变
React immutable不可变,不可通过this.state = xxx来修改,只能通过setstate
Vue是数据mutable可变,响应式数据双向绑定
5、语法不同
React:只有jsx语法
Vue:sfc(template、js、css)也有jsx
一、React简介
React 是一个用于构建用户界面的 JavaScript 库。把用户界面抽象成一个个的组件,通过引入jsx语法,复用组件更容易,同时保证组件结构清晰。
React 特点
1. 专注于视图层
React不是完成的MVC/MVVM框架,很多人认为 React 是 MVC 中的 V(视图),但是你也可以把react理解为MVC模式
- controller中注册model和view
- view向model注册
- model更新数值通知view
2. Vitual DOM
React把真实的DOM树转换成js对象树,也就是Vitual DOM,通过对DOM的模拟,最大限度地减少与DOM的交互,因此更加高效。
每次数据更新后,重新计算Vitual DOM,并和上一次生成的Vitual DOM做对比,对发生变化的部分批量更新。
React类组件也提供了直观的shouldComponentUpdate
生命周期回调,函数式组件提供了useEffect
,来减少数据变化后不必要的Vitual DOM对比过程。
3. 函数编程
React采用声明范式
,可以轻松描述应用。充分利用函数式方法减少冗余代码,此外,本身就是简单函数,易于测试。函数式编程才是React精髓。声明式和命令式不同,声明式侧重于结果,而命令式侧重过程。
4. 单向响应的数据流
React 实现了单向响应的数据流
,从而减少了重复代码,这也是它为什么比传统数据绑定更简单
5. 组件
通过 React 构建组件,使得代码更加容易得到复用
,能够很好的应用在大项目的开发中。
6. 灵活
React可以与已知的库或框架很好地配合。
二、JSX语法
React 使用 JSX 来替代常规的 JavaScript。
JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
我们不需要一定使用 JSX,但它有以下优点:
- (1)JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- (2)它是类型安全的,在编译过程中就能发现错误。
- (3)使用 JSX 编写模板更加简单快速。
1、使用JSX基础
1.1. XML语法
JSX是将ui和逻辑层耦合在组件里,使用{}标识
类XML语法的ECMAScript扩展,使用XML好处是标签可以任意嵌套,可以像写HTML一样清晰的看到DOM树状结构及其属性。
const list = () => {
<div>
<Title>这是标题</Title>
<ul>
<li>列表item</li>
<li>列表item</li>
<li>列表item</li>
</ul>
</div>
}
上面构造了一个list组件,注意事项:
- 定义标签时,只允许被一个标签包裹,例如:
const list = () => {
<Title>这是标题</Title>
<ul>
<li>列表item</li>
<li>列表item</li>
<li>列表item</li>
</ul>
}
这样就会报错了,原因是没有最外层包裹,无法转译成功。
- 标签一定要闭合
当然,JSX报错机制很强大,如果有拼写错误会直接打印出来。
1.2. 元素类型
在构建页面中有两种元素:DOM元素和组件元素,在JSX中会有对应,对应规则是标签的首字母是否是小写字母,小写首字母对应DOM元素,大写首字母对应组件元素。
1.3. 注释
注释需要写在花括号中,实例如下:
const list = () => {
<div>
<Title>这是标题</Title>
{/*注释...*/}
<ul>
<li>列表item</li>
<li>列表item</li>
<li>列表item</li>
</ul>
</div>
}
1.4. 元素属性
JSX语法中DOM元素属性有两个例外,class和for
- class属性改为className
return (
<div>
<div className="hot-box">
<ul className="hot-list">
<SubThree/>
</ul>
</div>
</div>
)
- for属性改为html-for
2、jsx=>babel编译全流程
React.createElement
函数用于创建一个虚拟DOM(也称为元素)节点,而 render 函数负责将虚拟DOM渲染到真实的DOM中。
// 在React中,jsx=>babel编译全流程:
// 这里是一个简单的例子:
// 引入React和ReactDOM
import React from 'react';
import ReactDOM from 'react-dom';
// 使用React.createElement创建一个虚拟DOM元素
const element = React.createElement('h1', {className: 'greeting'}, 'Hello, world!');
// React.createElement包含type/props/children
// 使用ReactDOM.render将虚拟DOM元素渲染到页面的某个部分即实际dom
ReactDOM.render(element, document.getElementById('root'));
// 二次更改虚拟dom时,通过diff算法,比对虚拟dom,触发更新
3、 flushSync强制同步
调用 flushSync 强制 React 刷新所有挂起的工作,并同步更新 DOM。
在react18版本后setState方法都是异步
的,同一动作内多个setState会被批处理
如果想在18以后版本内使用 setState 同步,可以使用flushSync
但是使用 flushSync 是不常见的行为,并且可能损伤应用程序的性能,尽量不要使用
import { flushSync } from 'react-dom';
flushSync(() => {
setSomething(123);
});
// 类组件内如何拿到 setState 的值,进行下一个 setState 的更新?
this.setState((state) => {
count: state.count + 1
})
三、React组件
React组件就是组件元素,组件元素被描绘成纯粹的JSON对象,意味着可以使用方法或类来进行构建。
React组件由三部分构成–属性(props)、状态(state)以及生命周期方法。如下图:
React组件构建方法
组件首字母大写,会被当作变量渲染成dom,否则被当成字符串编译。
react三大组件:类组件、函数式组件和高阶组件
其中高阶组件HOC(higher ordered component)在进阶篇中介绍
1、 类组件
import React from 'react';
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increment
</button>
</div>
);
}
}
export default ClassComponent;
2、函数式组件
import React, { useState } from 'react';
function FunctionalComponent() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default FunctionalComponent;
四、React数据流
在React中,数据是自顶向下单向流动的,即从父组件到子组件。
state
和props
是React组件中最重要的概念。如果顶层组件初始化props
,那么React会向下遍历整颗组件树,重新尝试渲染所有相关的子组件。而state
只关心每个组件内部状态,这些状态只能在组件内改变。把组件看成一个函数,那么props
作为参数,内部state
作为内部参数,返回一个Vitual DOM
的实现。
1、state
1.1 state基础
- React中state用于管理组件内部状态
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
- setState是一个异步方法,一个生命周期内所有的setState方法会合并操作。
- 我们并不推荐滥用state,过多的状态会让数据流混乱,程序变得难以维护。
1.2 state更新方式
(1)函数式组件
在函数式组件中,setState
是通过使用 React 的 useState Hook
获取的。useState
返回一个数组,其中包含当前状态值和一个用于更新该状态的函数。你可以通过解构赋值获取这两个值。
以下是一个简单的例子,展示了如何在函数式组件中使用 setState
:
import React, { useState } from 'react';
function ExampleComponent() {
const [count, setCount] = useState(0);
const incrementCount = () => {
// 获取当前 count 的值,并加一
setCount(count + 1);
};
const decrementCount = () => {
// 获取当前 count 的值,并减一
setCount(count - 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementCount}>Increment</button>
<button onClick={decrementCount}>Decrement</button>
</div>
);
}
export default ExampleComponent;
在上面的代码中,incrementCount
和 decrementCount
函数在点击按钮时被调用,并且它们使用当前的 count
值来计算下一个状态值。这是因为 count
是函数组件的本地状态,并且每次状态更新后都会重新渲染组件。
(2)类组件
可以通过this.state来访问setState的值,但要注意的是,setState是异步的,所以如果你需要基于一个状态更新另一个状态,你应该使用setState的回调函数形式,该函数会在状态更新完成后被调用,并且新的状态值会作为参数传入。下面是一个例子:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0,
doubleValue: 0,
};
}
// 假设我们要将value状态值翻倍,并更新doubleValue状态
updateDoubleValue = () => {
this.setState(
{ value: this.state.value + 1 },
() => {
this.setState({ doubleValue: this.state.value * 2 });
}
);
}
render() {
return (
<div>
<button onClick={this.updateDoubleValue}>Update Double Value</button>
<p>Value: {this.state.value}</p>
<p>Double Value: {this.state.doubleValue}</p>
</div>
);
}
}
在上面的例子中,我们有一个MyComponent
类组件,它有两个状态:value
和doubleValue
。我们定义了一个方法updateDoubleValue
,在这个方法中,我们首先通过setState
将value
的值增加1
,然后在setState
的回调函数中,我们根据更新后的value
值(此时等于新的状态值),计算出doubleValue
的新值,并用setState
进行更新。
1.3 setState版本变更
- v18之前
(1)在组件生命周期或者react合成事件中setState都是异步的
(2)setTimeout或者原生dom事件setState是同步的 - v18之后
都是异步的
多个setState会合并处理
2、props
React是单向数据流,主要的流通管道就是props。
所有react组件都要像纯函数
一样去保护props
不被更改
state 和 props 主要的区别在于 props 是不可变
的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 props 来传递数据。
2.1 props父向子传值
function HelloMessage(props) {
return <h1>Hello {props.name}!</h1>;
}
const element = <HelloMessage name="Runoob"/>;
由于自定义组件的参数是可以自定义的,在上面代码中父组件中使用子组件HelloMessage,参数name传值,子组件使用props.name拿到参数值
2.2 用function prop向父组件通信
首先我们创建一个tab的子组件作为例子
import React, {Component} from 'react';
import { Tabs, WhiteSpace, Badge } from 'antd-mobile';
import 'antd-mobile/dist/antd-mobile.css';
const tabs = [
{ title: '今日推荐', sub: '1' },
{ title: '福利', sub: '2' },
{ title: '热点', sub: '3' },
{ title: '信用卡', sub: '4`' },
];
class TheTab extends Component {
constructor(props) {
super(props)
}
handleClick (e)=> {
this.props.onClick(e.sub)
}
render() {
return (
<div>
<Tabs tabs={tabs}
initialPage={0}
onChange={this.handleClick}
>
</Tabs>
<WhiteSpace />
</div>
)
}
}
export default TheTab;
当用户点击tab的item,会触发handleClick方法,在handleClick方法中通过prop调用父组件的onClick方法,并且传递参数。
这时我们看一下父组件中的部分代码:
handleClick = (id) =>{
console.log(id)
this.setState({
tabId: id
})
}
render() {
return (
<div className="App">
<TheTab onClick={this.handleClick}/>
</div>
)
}
通过触发父组件的onClick方法就实现了对tabId值的修改。·
五、React生命周期
React生命周期可以分为挂载、渲染和卸载几个阶段。当渲染后的组件需要更新时会重新渲染组件,直至卸载。
生命周期分为两类:
- 当组件挂载和卸载时
- 当组件接受新数据,即组件更新时
1、组件挂载时
componentWillMount()
let httpUrl = '******'
this.getData(httpUrl);
}
componentDidMount() {}
componentWillMount 在render之前执行,初始化的state一般不会在这里setState,因为组件更新state但只渲染一次,没有意义,初始化的state都可以放在this.state中。
componentDidMount在render之后执行,如果在这里执行setState方法,组件会再次更新,那么在初始化过程中就两次渲染了组件,这不是一件好事。
但是:
实际情况是,一些场景不得不setState,如计算组件的位置和宽高,不得不先渲染组件,更新部分数据后再次渲染。
2、组件卸载时
componentWillUnMount() {}
在这个生命周期,往往执行一些清理操作,如事件回收和清理定时器。
3、数据更新过程
更新过程指的是,父组件向下传递props或组件自身执行setState方法时执行的一系列动作。
improt React, {Component, PropTypes} from 'react';
class App extends Component{
componentWillReceiveProps(newProps) {
console.log('Component WILL RECEIVE PROPS!')
}
shouldComponentUpdate(newProps, newState) {
return true;
}
componentWillUpdate(nextProps, nextState) {
console.log('Component WILL UPDATE!');
}
componentDidUpdate(prevProps, prevState) {
console.log('Component DID UPDATE!')
}
render() {
return <div>this is a demo </div>;
}
}
如果组件自身的state更新了,会依次执行componentWillReceiveProps、 componentWillUpdate、 render 和 componentDidUpdate
这几个生命周期要注意的点有:
3.1 shouldComponentUpdate判断更新
shouldComponentUpdate是一个特别的方法,它接受需要更新的props和state,让开发者增加条件判断,需要时更新,不需要时不更新。因此,当方法返回false时,组件不再向下执行生命周期方法。
正确的组件渲染,是性能优化的手段之一。
3.2 无状态组件
无状态组件没有生命周期
3.3 componentWillUpdate
不能在componentWillUpdate中执行setState
3.4 componentWillReceiveProps
如果在父组件传入props而更新的,componentWillReceiveProps先执行,此方法可以作为React在props传入后,渲染之前setState的机会,在此方法中调用setState是不会二次渲染的。