步步为营--深入研究react技术栈 系列一 (jsx、组件、数据流)
相关技术博客:
1、步步为营–深入研究react技术栈 系列一 (jsx、组件、数据流)
2、步步为营–深入研究react技术栈 系列二 (事件、组件性能优化)
更多系列文章持续更新中…
前言—react与vue对比
相同点
1、虚拟dom
2、基于组件的开发模式
3、专注于视图
不同点
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 特点
- 专注于视图层
React不是完成的MVC/MVVM框架,很多人认为 React 是 MVC 中的 V(视图),但是你也可以把react理解为MVC模式
- controller中注册model和view
- view向model注册
- model更新数值通知view
-
Vitual DOM
React把真实的DOM树转换成js对象树,也就是Vitual DOM,通过对DOM的模拟,最大限度地减少与DOM的交互,因此更加高效。
每次数据更新后,重新计算Vitual DOM,并和上一次生成的Vitual DOM做对比,对发生变化的部分批量更新。
React类组件也提供了直观的shouldComponentUpdate生命周期回调,函数式组件提供了useEffect,来减少数据变化后不必要的Vitual DOM对比过程。 -
函数编程
React采用声明范式,可以轻松描述应用。充分利用函数式方法减少冗余代码,此外,本身就是简单函数,易于测试。函数式编程才是React精髓。
声明式和命令式不同,声明式侧重于结果,而命令式侧重过程。
- 单向响应的数据流 − React 实现了
单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单 - 组件 − 通过 React 构建组件,使得代码
更加容易得到复用,能够很好的应用在大项目的开发中。 - 灵活 −React可以与已知的库或框架很好地配合。
二、JSX语法
React 使用 JSX 来替代常规的 JavaScript。
JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
我们不需要一定使用 JSX,但它有以下优点:
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 它是类型安全的,在编译过程中就能发现错误。
- 使用 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,否则被当成字符串编译。
1、 React.createClass
用React.createClass方法构建组件是React最传统也是兼容性最好的方法。
const Btn = React.createClass({
getDefaultProps() {
return {
color: 'blue',
text: 'confirm'
}
}
render() {
const {color, text} = this.props
return ({
<button className={`btn btn-&{color}`}>
<em>{text}</em>
</button>
})
}
})
2、 ES6 classes
class Welcome extends React.Component {
render() {
return <h1>Hello World!</h1>;
}
}
3、无状态函数
无状态函数构建的组件叫无状态组件,这种方法是0.14版本之后新增的,官方颇为推崇。
function HelloMessage(props) {
return <h1>Hello World!</h1>;
}
无状态组件没有state,也没有生命周期方法。
在适当的情况下,我们都应该且必须使用无状态组件,它创建时始终保持一个实例,避免了不必要的检查和内存分配,做到了内部优化。
四、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版本变更
1、v18之前
(1)在组件生命周期或者react合成事件中setState都是异步的
(2)setTimeout或者原生dom事件setState是同步的
2、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是不会二次渲染的。
更多推荐
所有评论(0)