Skip to main content

类组件渲染流程-组件分类

组件的定义方式来划分

  • 函数组件(Functional Component)
  • 类组件(Class Component)

组件内部是否有状态

有状态组件(Stateful Component)

有状态组件:有状态组件也可以叫做容器组件

主要用来定义交互逻辑和业务数据(如果用了Redux,可以把业务数据抽离出去统一管理).

使用{this.state.xxx}的表达式把业务数据挂载到容器组件的实例上(有状态组件也可以叫做容器组件,无状态组件也可以叫做展示组件),然后传递props到展示组件,展示组件接收到props,把props塞到模板里面。

无状态组件:Stateless Component

无状态组件:无state,有html

  1. 无状态组件主要用来定义模板,接收来自父组件props传递过来的数据,使用{props.xxx}的表达式把props塞到模板里面。

无状态组件应该保持模板的纯粹性,以便于组件复用。创建无状态组件如下:

const Header = (props) = (
<div>{props.xxx}</div>
);

无状态组件运用-一般会搭配高阶组件(简称:HOC)一起使用

无状态组件:无state,主要作用是用于展示UI,接收来自父组件props传递过来的数据

无状态组件一般会搭配高阶组件(简称:HOC)一起使用

例如:无状态组件在需要生命周期时,可以通过高阶组件来实现:

  1. 无状态组件作为高阶组件的参数,并在最后通过调用高阶函数导出一个高阶组件
// 这是一个无状态组件 TableComponent 
import React from 'react'
import { Table } from './table' // 高阶函数
const TableComponent = (props) => {
return (
<div>
{props.dataSource}
</div>
)
}

export default Table(TableComponent);


// 写一个高阶组件,里面写任何需要的生命周期
import React from 'react'
export const Table = (ComposedComponent) => {
return class extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
console.log('componentDidMount');
}
render() {
return (
<ComposedComponent {...this.props}/>
)
}
}
}

组件的不同职责来划分

展示型组件(Presentational Component 只展示,业务逻辑较少)

展示组件数据来源:props;--->通过无状态组件实现

有html,负责展示UI,也就是组件如何渲染,具有很强的内聚性。
只关心得到数据后如何渲染

数据来源:props作用:描述如何展现
数据修改:从props调用回调函数

容器型组件(Container Component 有着复杂的业务逻辑)

  1. 通过有状态组件实现,将数据传给展示组件
  2. 没有html,容器组件用来包含展示其它组件或其它容器组件
负责应用逻辑处理
eg:
发送网络请求,处理返回数据,将处理过的数据传递给展示组件
也提供修改数据源的方法,通过展示组件的props传递给展示组件
当展示组件的状态变更引起源数据变化时,展示组件通过调用容器组件提供的方法同步这些变化

受控组件来划分

受控组件

有state;受控组件的特性在于元素的内容通过组件的状态state进行控制

双向数据绑定就是受控组件,受控组件更新state的流程:

  • 可以通过初始state中设置表单的默认值
  • 每当表单的值发生变化时,调用onChange事件处理器
  • 事件处理器通过事件对象e拿到改变后的状态,并更新组件的state
  • 一旦通过setState方法更新state,就会触发视图的重新渲染,完成表单组件的更新

非受控组件

不需要设置它的state属性,而通过ref来操作真实的DOM

PureComponent定义

PureComponent 和 Component 的最大区别在于,PureComponent 实现了 shouldComponentUpdate 方法,用来浅比较组件的 props 和 state,如果这些值没有发生变化,就不会触发组件的重新渲染,从而提高组件的性能。这个过程是自动完成的,开发者无需手动编写 shouldComponentUpdate 方法。

PureComponent 和 Component 的最大区别在于,PureComponent 实现了 shouldComponentUpdate 方法,用来浅比较组件的 props 和 state,如果这些值没有发生变化,就不会触发组件的重新渲染,从而提高组件的性能。这个过程是自动完成的,开发者无需手动编写 shouldComponentUpdate 方法。

import React, { PureComponent } from 'react';

class MyComponent extends PureComponent {
render() {
return <div>{this.props.text}</div>;
}
}

export default MyComponent;

类组件定义

  • 类组件需要继承自 React.Component 或 React.PureComponent
  • 类组件必须实现render函数
  • 组件的名称是大写字符开头,函数组件要大写
<script type="text/babel">
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = { liked: false };
}

render() {
console.log('render start')
if (this.state.liked) {
return (
<div onClick={() => this.setState({ liked: !this.state.liked })}>click me again</div>);
}
return (
<div>
<button onClick={() => this.setState({ liked: !this.state.liked })}>
Like1
</button>
<p>这是兄弟节点</p>
</div>
);
}
}
const container = document.getElementById('root');
const astTree = React.createElement(LikeButton)
console.log('createElement 转化后ast树的结构:', astTree)
// debugger
const root = ReactDOM.createRoot(container);
root.render(astTree);
</script>

参考:useEffect

类组件中定义常量

你可以在类的任何地方使用const关键字来定义常量。常量通常用于存储不会改变的数值或引用,例如配置项、枚举值等。

你可以在类的构造函数中定义常量,也可以在类的成员方法之外的任何地方定义常量。

import React, { Component } from 'react';

class MyComponent extends Component {
constructor(props) {
super(props);

// 定义常量使用const关键字
const MY_CONSTANT = 'constant value';

// 绑定函数到当前实例
this.myFunction = this.myFunction.bind(this);
}

// 定义常量在类的成员方法之外
static MY_CONSTANT2 = 'constant value';

// 定义函数
myFunction() {
// 使用常量
console.log(MY_CONSTANT);

// 使用常量
console.log(MyComponent.MY_CONSTANT2);

// 执行操作
console.log('Function executed');
}

render() {
return (
<div>
<button onClick={this.myFunction}>Click me</button>
</div>
);
}
}

export default MyComponent;

类组件中定义function

在React类组件中的函数可以不绑定到当前实例,但这取决于函数在类方法中的使用方式以及JavaScript的this语境。绑定函数到当前实例是为了确保在函数内部可以访问类的实例属性和方法。

如果你不绑定函数到当前实例,并且尝试在函数内部访问this,this的值可能不会如预期那样指向类的实例。这是因为JavaScript中的this的值取决于函数的调用方式。如果函数未正确绑定到类实例,this可能会指向undefined或全局对象,这可能导致错误或不正确的行为。

有两种主要方法来绑定函数到当前实例

区别在于:

绑定函数到当前实例: 这是传统的方式,使用.bind(this)来确保函数内部的this始终指向当前实例。这适用于大多数情况,但可能会稍微增加内存和性能开销,因为每次函数被调用时都会创建一个新的绑定函数。

使用箭头函数: 这种方式更简洁,不需要显式绑定,但它会创建一个新的箭头函数,因此可能稍微增加内存使用。箭头函数在定义时捕获this的值,因此它们在函数内部始终引用当前实例。

通常情况下,你可以选择使用箭头函数,因为它更简洁,但请注意这可能会带来一些微小的性能开销。

  1. 使用.bind(this):在构造函数中或在需要绑定的地方使用.bind(this)方法,如下所示:
constructor(props) {
super(props);
this.myFunction = this.myFunction.bind(this);
}

myFunction() {
// 在这里可以安全地使用this来引用类的实例
}
  1. 使用箭头函数:箭头函数会自动捕获外部this的值,所以你不需要显式绑定,例如:
myFunction = () => {
// 在这里也可以安全地使用this来引用类的实例
}

类组件和函数组件定义区别

function MyComponent(props) {
return <div>Hello, {props.name}!</div>;
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}

handleClick() {
this.setState({ count: this.state.count + 1 });
}

render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.handleClick()}>Increment</button>
</div>
);
}
}

探究类组件的构建fiber树

测试实例:例8-测试函数组件-18.html

beginWork进入

如下例函数renderRootSync debugger

  function renderRootSync(root, lanes) {
var prevExecutionContext = executionContext;
executionContext |= RenderContext;
var prevDispatcher = pushDispatcher(); // If the root or lanes have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.

if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
{
if (isDevToolsPresent) {
var memoizedUpdaters = root.memoizedUpdaters;

if (memoizedUpdaters.size > 0) {
restorePendingUpdaters(root, workInProgressRootRenderLanes);
memoizedUpdaters.clear();
} // At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
// If we bailout on this work, we'll move them back (like above).
// It's important to move them now in case the work spawns more work at the same priority with different updaters.
// That way we can keep the current update and future updates separate.


movePendingFibersToMemoized(root, lanes);
}

}


// console.log('workInProgress', workInProgress, root)
debugger
console.log('render调用 prepareFreshStack前', workInProgress)
workInProgressTransitions = getTransitionsForLanes();
prepareFreshStack(root, lanes);
console.log('render调用 prepareFreshStack后', workInProgress)
}

{
markRenderStarted(lanes);
}

do {
try {
console.log('%c=render阶段准备:', 'color:red', 'renderRootSync()调用workLoopSync()-root:', { root });
debugger
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// 省略
}
switch (workInProgress.tag) {
case ClassComponent:
{
var _Component = workInProgress.type;
var _unresolvedProps = workInProgress.pendingProps;

var _resolvedProps = workInProgress.elementType === _Component ? _unresolvedProps : resolveDefaultProps(_Component, _unresolvedProps);
console.log('%c=beginWork()=end 5 updateClassComponent', 'color:magenta')
return updateClassComponent(current, workInProgress, _Component, _resolvedProps, renderLanes);
}
}

_Component

function LikeButton(props) {
_classCallCheck(this, LikeButton);

var _this = _possibleConstructorReturn(this, (LikeButton.__proto__ || Object.getPrototypeOf(LikeButton)).call(this, props));

_this.state = { liked: false };
return _this;
}
function updateClassComponent(current, workInProgress, Component, nextProps, renderLanes) {
// 省略
if (instance === null) {
if (current !== null) {
// A class component without an instance only mounts if it suspended
// inside a non-concurrent tree, in an inconsistent state. We want to
// treat it like a new mount, even though an empty version of it already
// committed. Disconnect the alternate pointers.
current.alternate = null;
workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect

workInProgress.flags |= Placement;
} // In the initial pass we might need to construct the instance.


constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(workInProgress, Component, nextProps, renderLanes);
} else {
shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderLanes);
}

var nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes);

{
var inst = workInProgress.stateNode;

if (shouldUpdate && inst.props !== nextProps) {
if (!didWarnAboutReassigningProps) {
error('It looks like %s is reassigning its own `this.props` while rendering. ' + 'This is not supported and can lead to confusing bugs.', getComponentNameFromFiber(workInProgress) || 'a component');
}

didWarnAboutReassigningProps = true;
}
}

return nextUnitOfWork;
}

最后在这个函数调用函数组件的render函数->instance.render

function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes) {

// 省略
if (didCaptureError && typeof Component.getDerivedStateFromError !== 'function') {
// If we captured an error, but getDerivedStateFromError is not defined,
// unmount all the children. componentDidCatch will schedule an update to
// re-render a fallback. This is temporary until we migrate everyone to
// the new API.
// TODO: Warn in a future release.
nextChildren = null;

{
stopProfilerTimerIfRunning();
}
} else {
{
markComponentRenderStarted(workInProgress);
}

{
setIsRendering(true);
nextChildren = instance.render();

if (workInProgress.mode & StrictLegacyMode) {
setIsStrictModeForDevtools(true);

try {
instance.render();
} finally {
setIsStrictModeForDevtools(false);
}
}

setIsRendering(false);
}

{
markComponentRenderStopped();
}
}
// 省略
}

类组件也在这里赋值memoizedState

workInProgress.memoizedState = instance.state; // The context might have changed so we need to recalculate it.

调用render

nextChildren = instance.render();

也就是类组件的render函数

render() {
console.log('render start')
if (this.state.liked) {
return (
<div onClick={() => this.setState({ liked: !this.state.liked })}>click me again</div>);
}
return (
<div>
<button onClick={() => this.setState({ liked: !this.state.liked })}>
Like1
</button>
<p>这是兄弟节点</p>
</div>
);
}

重点:return调用的是createElement,可见和函数组件是一样的也是babel编译后的createElement

react.development18.js

function createElement(type, config, children) {}

render函数的return debugger 进入的是createElementWithValidation,创建fiber节点

  function createElementWithValidation(type, props, children) {
var validType = isValidElementType(type); // We warn in this case but don't throw. We expect the element creation to
// succeed and there will likely be errors in render.

if (!validType) {
var info = '';

if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
info += ' You likely forgot to export your component from the file ' + "it's defined in, or you might have mixed up default and named imports.";
}

var sourceInfo = getSourceInfoErrorAddendumForProps(props);

if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}

var typeString;

if (type === null) {
typeString = 'null';
} else if (isArray(type)) {
typeString = 'array';
} else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = "<" + (getComponentNameFromType(type.type) || 'Unknown') + " />";
info = ' Did you accidentally export a JSX literal instead of a component?';
} else {
typeString = typeof type;
}

{
error('React.createElement: type is invalid -- expected a string (for ' + 'built-in components) or a class/function (for composite ' + 'components) but got: %s.%s', typeString, info);
}
}

var element = createElement.apply(this, arguments); // The result can be nullish if a mock or a custom function is used.
// TODO: Drop this when these are no longer allowed as the type argument.

if (element == null) {
return element;
} // Skip key warning if the type isn't valid since our key validation logic
// doesn't expect a non-string/function type and can throw confusing errors.
// We don't want exception behavior to differ between dev and prod.
// (Rendering will throw with a helpful message and as soon as the type is
// fixed, the key warnings will appear.)


if (validType) {
for (var i = 2; i < arguments.length; i++) {
validateChildKeys(arguments[i], type);
}
}

if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}

return element;
}

最后调用reconcileChildren

function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes) {
// 省略
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
 function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
console.log('%c=reconcileChildren mount', 'blueviolet');
// 对于 mount 的组件
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
console.log('%c=reconcileChildren mount 返回值workInProgress.child', 'blueviolet', workInProgress.child);
} else {
// 省略
}
}