Skip to main content

React 基础

1、 HOC

Higher-Order Component,简称 HOC

React 高阶组件是一种接受 React 组件作为参数并返回新组件的函数。

高阶组件的好处

  • 代码复用: 高阶组件可以帮助您在组件之间复用逻辑,从而减少代码冗余并提高开发效率。例如,您可以编写一个高阶组件来处理组件的数据获取逻辑,然后将其应用于所有需要获取数据的组件。
  • 封装性: 高阶组件可以帮助您将公共逻辑封装成独立的组件,从而提高代码的可维护性和可读性。例如,您可以编写一个高阶组件来处理组件的授权逻辑,然后将其应用于所有需要授权的组件。
  • 灵活性: 高阶组件可以为您提供一种灵活的方式来增强组件的功能。您可以使用高阶组件来添加新的生命周期方法、劫持事件、监控日志等。

高阶组件的示例

以下是一个简单的示例,演示如何使用高阶组件来添加日志功能:

function withLogging(Component) {
return class WithLogging extends Component {
render() {
console.log('Rendering component:', this.props);
return super.render();
}
};
}

const MyComponent = () => (
<div>Hello, world!</div>
);

const MyComponentWithLogging = withLogging(MyComponent);

<MyComponentWithLogging />

在这个示例中,withLogging 高阶组件接受一个组件作为参数并返回一个新组件。新组件包含原始组件的 render 方法,并在 render 方法之前添加了一些日志记录代码。

高阶组件的常见用法

  • 状态管理: 高阶组件可以用于将状态管理逻辑与组件分离,例如使用 Redux 或 MobX。
  • 数据获取: 高阶组件可以用于获取组件所需的数据,例如使用 Axios 或 fetch API。
  • 访问控制: 高阶组件可以用于控制组件的访问权限,例如使用 React Router 或 Auth0。
  • 表单验证: 高阶组件可以用于验证表单输入,例如使用 Formik 或 React Hook Form。

2、 React 的事件冒泡

点击查看 Demo

3、 Redux 实现原理

createStore.js
// 模拟 createStore 函数,创建 Redux store
function createStore(reducer, initialState) {
let state = initialState;
let listeners = [];

function getState() {
return state;
}

function dispatch(action) {
state = reducer(state, action);
listeners.forEach(listener => listener());
}

function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
listeners = listeners.filter(l => l !== listener);
};
}

dispatch({ type: '@@redux/INIT' });

return {
getState,
dispatch,
subscribe
};
}

connect.js
// 模拟 connect 函数,连接 Redux store 和 React 组件
function connect(mapStateToProps, mapDispatchToProps) {
// 返回一个高阶组件
return function (WrappedComponent) {
return class extends React.Component {
componentDidMount() {
// 订阅 Redux store 的状态变化
this.unsubscribe = store.subscribe(this.handleChange);
}

componentWillUnmount() {
// 取消订阅 Redux store
this.unsubscribe();
}

handleChange = () => {
// 当 Redux store 的状态发生变化时,更新组件的状态
this.forceUpdate();
};

render() {
// 获取 Redux store 的状态
const state = store.getState();

// 根据 mapStateToProps 函数获取组件所需的状态
const stateProps = mapStateToProps ? mapStateToProps(state) : {};

// 根据 mapDispatchToProps 函数获取组件所需的操作
const dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch) : {};

// 合并 stateProps 和 dispatchProps,传递给 WrappedComponent
return <WrappedComponent {...this.props} {...stateProps} {...dispatchProps} />;
}
};
};
}
reducer.js

// 示例的 Redux reducer
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}

app.jsx
// 创建 Redux store
const store = createStore(counterReducer);

// React 组件
class Counter extends React.Component {
render() {
return (
<div>
<p>Count: {this.props.count}</p>
<button onClick={this.props.increment}>Increment</button>
<button onClick={this.props.decrement}>Decrement</button>
</div>
);
}
}

// 连接 Counter 组件到 Redux store
const ConnectedCounter = connect(
// mapStateToProps 函数,将 Redux store 的状态映射到组件的 props 上
state => ({
count: state.count
}),
// mapDispatchToProps 函数,将操作映射到组件的 props 上
dispatch => ({
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
})
)(Counter);

// 渲染应用
ReactDOM.render(
<Provider store={store}>
<ConnectedCounter />
</Provider>,
document.getElementById('root')
);

4、 React 生命周期

组件的生命周期可分成三个状态:

  • Mounting(挂载):已插入真实 DOM
  • Updating(更新):正在被重新渲染
  • Unmounting(卸载):已移出真实 DOM

4.1、 挂载

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  • constructor: 在 React 组件挂载之前,会调用它的构造函数。
  • getDerivedStateFromProps: 用途:让组件在 props 变化时更新 state。在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
  • render: render() 方法是 class 组件中唯一必须实现的方法。
  • componentDidMount: 在组件挂载后(插入 DOM 树中)立即调用。

render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

4.2、 更新

每当组件的 state 或 props 发生变化时,组件就会更新。

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • getDerivedStateFromProps: 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
  • shouldComponentUpdate:当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
  • render: render() 方法是 class 组件中唯一必须实现的方法。
  • getSnapshotBeforeUpdate: 在最近一次渲染输出(提交到 DOM 节点)之前调用。
  • componentDidUpdate: 在更新后会被立即调用。

4.3、 卸载

当组件从 DOM 中移除时会调用如下方法:

  • componentWillUnmount: 在组件卸载及销毁之前直接调用。

4.4、 父子组件生命周期

点击查看 Demo 代码详情

class A extends React.Component {
render() {
<B />
}
}

初始化阶段:

A constructor
A getDerivedStateFromProps
A render
B constructor
B getDerivedStateFromProps
B componentDidMount
A componentDidMount

更新阶段:

A getDerivedStateFromProps
A shouldComponentUpdate
A render
B getDerivedStateFromProps
B shouldComponentUpdate
A getSnapshotBeforeUpdate
A componentDidUpdate

4.5、 react16.7 废弃了几个 生命周期

  • componentWillmount
  • componentWillUpdate
  • componentWillReceiveProps

引入了 getDerivedStateFromPropgetDerivedStateFromProp就是一个static函数,在里面拿不到this也无法setState,更符合纯函数的概念

5、 子组件向父组件传值

App.jsx
import React, { PureComponent } from 'react';
export default class Children extends PureComponent {
onChange = (newText) => {
this.setState({ text: newText })
};
render(){
return(
<div>
<div>{this.state.text}</div>
<Button handleData={this.onChange}> 向父组件传值 </Button>
</div>
)
}
}

Button.jsx
import React, { PureComponent } from 'react';
export default class Children extends PureComponent {
onClick = () => {
this.props.handleData('text2');
};
render(){
return(
<div>
<h2 onClick={this.onClick}>onClick</h2>
</div>
)
}
}

6、 父组件调用子组件的方法

通过refs:在子组件上声明一个ref值,那么在需要调用时,使用this.refs的方式即可调用子组件的方法。

查看代码示例

App.jsx
class App extends React.Component {
constructor() {
super();
this.ref = React.createRef();
}
onClick = () => {
// this.ref.onShow();
this.ref.current.onShow();
}
render() {
return <div>
<h1 onClick={this.onClick}>hello react</h1>
<B ref={this.ref} />
</div>;
}
}
B.jsx
class B extends React.Component {
onShow = () => {
console.log(1);
};
render() {
return null;
}
}

7、 React 原理

7.1、 虚拟 DOM

虚拟 DOM 是 React 组件在内存中生成的一个对象,它描述了 DOM 节点。

比如 DOM 中的节点是这样的

<ul id="list"> 
<li class="item">A</li>
<li class="item">B</li>
<li class="item">C</li>
</ul>

虚拟 DOM 节点是这样的:

let oldVDOM = {
children: [
{
tagName: 'li', props: { class: 'item' }, children: ['A']
},
{
tagName: 'li', props: { class: 'item' }, children: ['B']
},
{
tagName: 'li', props: { class: 'item' }, children: ['C']
},
]
}

然后我修改了 DOM 节点中的内容,比如把 A 修改为 D,那么虚拟 DOM 节点就会变成这样:

let oldVDOM = {
children: [
{
tagName: 'li', props: { class: 'item' }, children: ['D']
},
{
tagName: 'li', props: { class: 'item' }, children: ['B']
},
{
tagName: 'li', props: { class: 'item' }, children: ['C']
},
]
}

7.2、 Diff算法

Diff算法是一种对比算法。对比两者是旧虚拟DOM和新虚拟DOM,对比出是哪个虚拟节点更改了,并只更新这个虚拟节点所对应的真实节点

7.2.1、 diff 原则

新旧虚拟DOM对比的时候,Diff算法比较只会在同层级进行, 不会跨层级比较。 所以Diff算法是:深度优先算法。 时间复杂度:O(n)

7.2.2、 component diff

React 是基于组件构建应用的,对于组件间的比较所采取的策略也是非常简洁、高效的。

  • 如果是 同一类型的组件,按照原策略继续比较 Virtual DOM 树即可。
  • 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
  • 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切知道这点,那么就可以节省大量的 diff 运算时间。因此,React允许用户通过shouldComponentUpdate()来判断该组件是否需进行diff算法分析,但是如果调用了 forceUpdate 方法,shouldComponentUpdate 则失效。

7.2.3、 element diff

当节点处于同一层级时,diff 提供了 3 种节点操作:插入、移动、删除。

  • 插入:新的组件类型不在旧集合里,即全新的节点,需要对新节点执行插入操作
  • 移动:旧集合中有新组件类型,且 element 是可更新的类型
  • 删除:旧集合中存在新组件类型,但 element 不再需要。或者旧组件不在新集合里

8、 Vue $nextTick VS React setState Callback

Vue 中 $nextTick 的实现:

  • 在 Vue 中,当数据发生变化时,Vue 会将更新 DOM 的操作放入微任务队列(Microtask Queue)中,而不是直接更新 DOM。
  • $nextTick 会将回调函数也放入微任务队列中,确保在 DOM 更新完成后再执行回调函数。
  • Vue 会先尝试使用原生的 Promise.resolve().then() 来实现微任务,如果浏览器不支持 Promise,则会使用 MutationObserversetImmediate 等备选方案。
export default {
name: 'Test',
data () {
return {
msg:"hello world",
}
},
methods: {
changeMsg() {
this.msg = "hello Vue" // vue数据改变,改变了DOM里的innerText
let msgEle = this.$refs.msg.innerText //后续js对dom的操作
console.log(msgEle) // hello world
// 输出可以看到data里的数据修改后DOM并没有立即更新,后续的DOM不是最新的

this.$nextTick(() => {
console.log(this.$refs.msg.innerText) // hello Vue
})
this.$nextTick().then(() => {
console.log(this.$refs.msg.innerText) // hello Vue
})
},
}
}

React 的 setState 是在状态更新后立即执行。但是,由于 React 的状态更新是异步的,因此在回调函数执行时,DOM 可能尚未完成更新。