在 react 当中,主要创建更新的有三种方式
1、ReactDOM.render || hydrate 
这两个api都是要把这个应用第一次渲染到我们页面上面,展现出来整个应用的样子的过程,这就是初始渲染
2、setState
3、forceUpdate
ReactDOM.render 需要做哪些事情呢?
1、创建 ReactRoot
2、创建 FiberRoot 和 RootFiber
3、创建更新
import React, { Component } from 'react'
import './App.css' class List extends Component {
state = {
a: 1,
b: 2,
c: 3,
} handleClick = () => {
this.setState(oldState => {
const { a, b, c } = oldState
return {
a: a * a,
b: b * b,
c: c * c,
}
})
} render() {
const { a, b, c } = this.state
return [
<span key="a">{a}</span>,
<span key="b">{b}</span>,
<span key="c">{c}</span>,
<button key="button" onClick={this.handleClick}>
click me
</button>,
]
}
} class Input extends Component {
state = {
name: 'jokcy',
} handleChange = e => {
// 这里如果使用方法设置`state`
// 那么需要现在外面读取`e.target.value`
// 因为在React走完整个事件之后会重置event对象
// 以复用event对象,如果等到方法被调用的时候再读取`e.target.value`
// 那时`e.target`是`null`
this.setState({
name: e.target.value,
})
} render() {
return (
<input
type="text"
style={{ color: 'red' }}
onChange={this.handleChange}
value={this.state.name}
/>
)
}
} class App extends Component {
render() {
return (
<div className="main">
<Input />
<List />
</div>
)
}
} export default App

这个 demo 非常简单,一个 Input  一个 List ,Input 里面有个 state ,属性有 name,然后 render 里面有个 input 标签,这个标签,输入的时候去改变这个 name 。

第二个组件是 List ,这个 List 里面有三个 span ,三个 span 里面分别显示 state 的属性 a,b,c ,然后点击 click ,a,b,c 以乘以自己的方式改变值。这就是这个 demo 的样子。 然后我们看一下最终要渲染这个应用,会调用 ReactDOM.render ,然后传入这个 App ,第二个是这个应用会挂载到的节点上
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'; ReactDOM.render(<App />, document.getElementById('root'))

我们这里传入的是 <App /> ,实际上我们传入的是 React.createElement。我们传进去的是 App 这个类,但是并没有创建这个实例,这个时候还什么东西都没有,因为我们只得到了一个 createElement,最终要形成一个页面渲染出来的过程,这就是 ReactDOM.render 要做的事情,然后看下这个源码,打开 ReactDOM.js。 react-dom 下面有不同的包,有 client , 有 server . 还有 shared , shared 是在 cilent 和 server 里面都会用到的。对应的就是渲染的平台不一样,server 就是在 Nodejs 下面进行渲染的一个工具包,我们主要了解的是 client 下面的东西。在 ReactDOM.js 下面,我们先找到定义 ReactDOM 的地方

const ReactDOM: Object = {
createPortal, findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {
if (__DEV__) {
let owner = (ReactCurrentOwner.current: any);
if (owner !== null && owner.stateNode !== null) {
const warnedAboutRefsInRender =
owner.stateNode._warnedAboutRefsInRender;
warningWithoutStack(
warnedAboutRefsInRender,
'%s is accessing findDOMNode inside its render(). ' +
'render() should be a pure function of props and state. It should ' +
'never access something that requires stale data from the previous ' +
'render, such as refs. Move this logic to componentDidMount and ' +
'componentDidUpdate instead.',
getComponentName(owner.type) || 'A component',
);
owner.stateNode._warnedAboutRefsInRender = true;
}
}
if (componentOrElement == null) {
return null;
}
if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
return (componentOrElement: any);
}
if (__DEV__) {
return DOMRenderer.findHostInstanceWithWarning(
componentOrElement,
'findDOMNode',
);
}
return DOMRenderer.findHostInstance(componentOrElement);
}, hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
// TODO: throw or warn if we couldn't hydrate?
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
}, render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}, unstable_renderSubtreeIntoContainer(
parentComponent: React$Component<any, any>,
element: React$Element<any>,
containerNode: DOMContainer,
callback: ?Function,
) {
invariant(
parentComponent != null && ReactInstanceMap.has(parentComponent),
'parentComponent must be a valid React Component',
);
return legacyRenderSubtreeIntoContainer(
parentComponent,
element,
containerNode,
false,
callback,
);
}, unmountComponentAtNode(container: DOMContainer) {
invariant(
isValidContainer(container),
'unmountComponentAtNode(...): Target container is not a DOM element.',
); if (container._reactRootContainer) {
if (__DEV__) {
const rootEl = getReactRootElementInContainer(container);
const renderedByDifferentReact =
rootEl && !ReactDOMComponentTree.getInstanceFromNode(rootEl);
warningWithoutStack(
!renderedByDifferentReact,
"unmountComponentAtNode(): The node you're attempting to unmount " +
'was rendered by another copy of React.',
);
} // Unmount should not be batched.
DOMRenderer.unbatchedUpdates(() => {
legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
container._reactRootContainer = null;
});
});
// If you call unmountComponentAtNode twice in quick succession, you'll
// get `true` twice. That's probably fine?
return true;
} else {
if (__DEV__) {
const rootEl = getReactRootElementInContainer(container);
const hasNonRootReactChild = !!(
rootEl && ReactDOMComponentTree.getInstanceFromNode(rootEl)
); // Check if the container itself is a React root node.
const isContainerReactRoot =
container.nodeType === ELEMENT_NODE &&
isValidContainer(container.parentNode) &&
!!container.parentNode._reactRootContainer; warningWithoutStack(
!hasNonRootReactChild,
"unmountComponentAtNode(): The node you're attempting to unmount " +
'was rendered by React and is not a top-level container. %s',
isContainerReactRoot
? 'You may have accidentally passed in a React root node instead ' +
'of its container.'
: 'Instead, have the parent component update its state and ' +
'rerender in order to remove this component.',
);
} return false;
}
}, // Temporary alias since we already shipped React 16 RC with it.
// TODO: remove in React 17.
unstable_createPortal(...args) {
if (!didWarnAboutUnstableCreatePortal) {
didWarnAboutUnstableCreatePortal = true;
lowPriorityWarning(
false,
'The ReactDOM.unstable_createPortal() alias has been deprecated, ' +
'and will be removed in React 17+. Update your code to use ' +
'ReactDOM.createPortal() instead. It has the exact same API, ' +
'but without the "unstable_" prefix.',
);
}
return createPortal(...args);
}, unstable_batchedUpdates: DOMRenderer.batchedUpdates, unstable_interactiveUpdates: DOMRenderer.interactiveUpdates, flushSync: DOMRenderer.flushSync, unstable_flushControlled: DOMRenderer.flushControlled, __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
// Keep in sync with ReactDOMUnstableNativeDependencies.js
// and ReactTestUtils.js. This is an array for better minification.
Events: [
ReactDOMComponentTree.getInstanceFromNode,
ReactDOMComponentTree.getNodeFromInstance,
ReactDOMComponentTree.getFiberCurrentPropsFromNode,
EventPluginHub.injection.injectEventPluginsByName,
EventPluginRegistry.eventNameDispatchConfigs,
EventPropagators.accumulateTwoPhaseDispatches,
EventPropagators.accumulateDirectDispatches,
ReactControlledComponent.enqueueStateRestore,
ReactControlledComponent.restoreStateIfNeeded,
ReactDOMEventListener.dispatchEvent,
EventPluginHub.runEventsInBatch,
],
},
};

这段就是定义 ReactDOM 的地方。ReactDOM 是个对象,这个对象里面有 render 方法。这个 render 方法我们可以看到他接收的有三个参数。一个是 element ,就是 react element . 第二个是 container ,就是我们要挂载到哪一个节点上面。第三个是 callback ,callback 就是说这个应用渲染结束之后,他会调用这个 callback 。

然后里面直接调用了一个方法叫 legacyRenderSubtreeIntoContainer,然后传入了 null ,element,container,false,callback 这几个参数。
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
// TODO: Ensure all entry points contain this check
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
); if (__DEV__) {
topLevelUpdateWarnings(container);
} // TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
let root: Root = (container._reactRootContainer: any);
if (!root) {
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
DOMRenderer.unbatchedUpdates(() => {
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
});
} else {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
}
return DOMRenderer.getPublicRootInstance(root._internalRoot);
}

这个就是 legacyRenderSubtreeIntoContainer 这个方法,第一个参数 null ,他对应的是 parentComponent 这个参数

let root: Root = (container._reactRootContainer: any);

然后下面他获取了 root 这么一个变量,他通过 container , container 就是 DOMContainer ,就是 ReactDOM.render 里面的第二个参数,就是挂载的节点,它上面有没有 _reactRootContainer 这个属性,第一次渲染的时候,这个属性值肯定是没有的,接下来没有的话,他就会去创建这个属性值,通过调用 legacyCreateRootFromDOMContainer 这个方法

function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): Root {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
if (!shouldHydrate) {
let warned = false;
let rootSibling;
while ((rootSibling = container.lastChild)) {
if (__DEV__) {
if (
!warned &&
rootSibling.nodeType === ELEMENT_NODE &&
(rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
) {
warned = true;
warningWithoutStack(
false,
'render(): Target node has markup rendered by React, but there ' +
'are unrelated nodes as well. This is most commonly caused by ' +
'white-space inserted around server-rendered markup.',
);
}
}
container.removeChild(rootSibling);
}
}
if (__DEV__) {
if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
warnedAboutHydrateAPI = true;
lowPriorityWarning(
false,
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
}
}
// Legacy roots are not async by default.
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
}

我们看到这个方法,他接收两个参数,第一个是 DOMContainer , 第二个是 forceHydrate ,这个 forceHydrate 就是传入的第四个参数,也就是 render 方法里面的第四个参数,是 false , render 方法里面是写死的,是不是代表着永远都是 false 呢,这个主要对应的是另外一种情况,就是 hydrate 这个方法,hydrate 跟 render 本质是一样的,唯一的一个区别就是是否会调和 container 里面的 html 节点,是否要复用这些节点。主要是有服务端渲染的时候会使用 hydrate 这个 api。因为服务端渲染 和 客户端渲染 第一次得到的节点是一样的。这个时候如果可以复用这些节点,可以提高一定的性能。所以他两唯一的区别就是第四个参数是 true 还是 false 。

我们再回到 legacyCreateRootFromDOMContainer ,对于 render 来说,这个 forceHydrate 是 false。那么如果是 false 的情况,会去调用这个方法 shouldHydrateDueToLegacyHeuristic。
function shouldHydrateDueToLegacyHeuristic(container) {
const rootElement = getReactRootElementInContainer(container);
return !!(
rootElement &&
rootElement.nodeType === ELEMENT_NODE &&
rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
);
}

这个方法里面 return 的是 rootElement 。rootElement 是通过调用 getReactRootElementInContainer 得到的

function getReactRootElementInContainer(container: any) {
if (!container) {
return null;
} if (container.nodeType === DOCUMENT_NODE) {
return container.documentElement;
} else {
return container.firstChild;
}
}

这个方法先判断一下这个 container 是否存在,如果不存在, return,如果存在,再继续判断这个节点是否等于 DOCUMENT_NODE ,就是 document ,就是 window.document ,是否是等于的。如果是等于,就直接返回这个 document 。 如果不是等于,就返回这个 container 的子节点。再回到 shouldHydrateDueToLegacyHeuristic ,判断完他是否是个节点,而且是 ROOT_ATTRIBUTE_NAME,然后返回,这个不是特点重要,跟 react 整体更新没有多大关系,再回到 legacyCreateRootFromDOMContainer 。当这个节点是 false 的情况下,执行了一个 for 循环,这个 for 循环是干嘛的呢?就是把 container 传进来的所有子节点都删掉,因为我们认为 shouldHydrate 为 false 的情况下,在渲染出来后,这些子节点已经不能用了,因为不需要合并他。最后return 一个 new ReactRoot。他传进去的是 container, isConcurrent, shouldHydrate 。 isConcurrent 我们看到直接是 false,已经注明了 root 节点不应该是 async 的。接下来我们看一下 new 一个 root 的过程

function ReactRoot(
container: Container,
isConcurrent: boolean,
hydrate: boolean,
) {
const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
this._internalRoot = root;
}
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'render');
}
if (callback !== null) {
work.then(callback);
}
DOMRenderer.updateContainer(children, root, null, work._onCommit);
return work;
};
ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'render');
}
if (callback !== null) {
work.then(callback);
}
DOMRenderer.updateContainer(null, root, null, work._onCommit);
return work;
};
ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'render');
}
if (callback !== null) {
work.then(callback);
}
DOMRenderer.updateContainer(children, root, parentComponent, work._onCommit);
return work;
};
ReactRoot.prototype.createBatch = function(): Batch {
const batch = new ReactBatch(this);
const expirationTime = batch._expirationTime; const internalRoot = this._internalRoot;
const firstBatch = internalRoot.firstBatch;
if (firstBatch === null) {
internalRoot.firstBatch = batch;
batch._next = null;
} else {
// Insert sorted by expiration time then insertion order
let insertAfter = null;
let insertBefore = firstBatch;
while (
insertBefore !== null &&
insertBefore._expirationTime <= expirationTime
) {
insertAfter = insertBefore;
insertBefore = insertBefore._next;
}
batch._next = insertBefore;
if (insertAfter !== null) {
insertAfter._next = batch;
}
} return batch;
};

ReactRoot 方法里面通过 DOMRenderer.createContainer(container, isConcurrent, hydrate) 创建了 root 节点。那么 DOMRenderer 是什么东西呢?

import * as DOMRenderer from 'react-reconciler/inline.dom';

DOMRenderer 其实就是 react-reconciler 下面的 inline.dom 。 inline.dom 里面只引用了 ReactFiberReconciler.js 。打开这个js 之后,找到 createContainer。

export function createContainer(
containerInfo: Container,
isConcurrent: boolean,
hydrate: boolean,
): OpaqueRoot {
return createFiberRoot(containerInfo, isConcurrent, hydrate);
}

发现这个方法里面创建了一个 createFiberRoot 。返回到 ReactRoot ,创建完之赋值给 this._internalRoot。然后再赋值给了 container._reactRootContainer ,最后,调用到了 root.render,就是原型链上的 render 方法。这里创建了一个 reactWork,这个不是特别重要,就跳过了。最终调用了 DOMRenderer.updateContainer。

export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current;
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}

updateContainer 他接收了 element,container,parentComponent。这个方法里面有三个变量。一个 current, 一个 currentTime。一个 expirationTime 。 expirationTime 是通过computeExpirationForFiber 获得的, expirationTime是非常重要的。然后调用 updateContainerAtExpirationTime 。

export function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// TODO: If this is a nested container, this won't be the root.
const current = container.current; if (__DEV__) {
if (ReactFiberInstrumentation.debugTool) {
if (current.alternate === null) {
ReactFiberInstrumentation.debugTool.onMountContainer(container);
} else if (element === null) {
ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
} else {
ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
}
}
} const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
} return scheduleRootUpdate(current, element, expirationTime, callback);
}
这里的 context 是没有的,最后调用了 scheduleRootUpdate。
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {
if (__DEV__) {
if (
ReactCurrentFiber.phase === 'render' &&
ReactCurrentFiber.current !== null &&
!didWarnAboutNestedUpdates
) {
didWarnAboutNestedUpdates = true;
warningWithoutStack(
false,
'Render methods should be a pure function of props and state; ' +
'triggering nested component updates from render is not allowed. ' +
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
'Check the render method of %s.',
getComponentName(ReactCurrentFiber.current.type) || 'Unknown',
);
}
} const update = createUpdate(expirationTime);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element}; callback = callback === undefined ? null : callback;
if (callback !== null) {
warningWithoutStack(
typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
update.callback = callback;
}
enqueueUpdate(current, update); scheduleWork(current, expirationTime);
return expirationTime;
}

这里面创建了 createUpdate。update 是用来标记,react 中需要标记更新的地点的,这个方法就是设置了一些update 相关的属性。最后调用 enqueueUpdate 。后面调用了 scheduleWork ,调度,为什么有调度呢,因为有任务优先级的概念。

React源码 ReactDOM.render的更多相关文章

  1. react源码之render

    1.最近学习react源码,刚刚入门,看了render的原理,到了fiberRoot的创建 如图:

  2. React源码剖析系列 - 生命周期的管理艺术

    目前,前端领域中 React 势头正盛,很少能够深入剖析内部实现机制和原理.本系列文章希望通过剖析 React 源码,理解其内部的实现原理,知其然更要知其所以然. 对于 React,其组件生命周期(C ...

  3. React 源码剖析系列 - 生命周期的管理艺术

    目前,前端领域中 React 势头正盛,很少能够深入剖析内部实现机制和原理. 本系列文章 希望通过剖析 React 源码,理解其内部的实现原理,知其然更要知其所以然. 对于 React,其组件生命周期 ...

  4. React躬行记(16)——React源码分析

    React可大致分为三部分:Core.Reconciler和Renderer,在阅读源码之前,首先需要搭建测试环境,为了方便起见,本文直接采用了网友搭建好的环境,React版本是16.8.6,与最新版 ...

  5. React源码之组件的实现与首次渲染

    react: v15.0.0 本文讲 组件如何编译 以及 ReactDOM.render 的渲染过程. babel 的编译 babel 将 React JSX 编译成 JavaScript. 在 ba ...

  6. react 源码之setState

    今天看了react源码,仅以记录. 1:monorepo (react 的代码管理方式) 与multirepo 相对. monorepo是单代码仓库, 是把所有相关项目都集中在一个代码仓库中,每个mo ...

  7. 读react源码准备

    git源码地址:https://github.com/facebook/react react 里面就是 react源码 react里面的react文件夹就是react源码,react源码非常的少,总 ...

  8. React源码 commit阶段详解

    转: React源码 commit阶段详解 点击进入React源码调试仓库. 当render阶段完成后,意味着在内存中构建的workInProgress树所有更新工作已经完成,这包括树中fiber节点 ...

  9. React源码解析:ReactElement

    ReactElement算是React源码中比较简单的部分了,直接看源码: var ReactElement = function(type, key, ref, self, source, owne ...

随机推荐

  1. [RN] React Native 使用 Redux 比较详细和深刻的教程

    React Native 使用 Redux 比较详细和深刻的教程 React Native 使用 Redux https://www.jianshu.com/p/06fc18cef56a http:/ ...

  2. Educational Codeforces Round 57 (Rated for Div. 2) D dp

    https://codeforces.com/contest/1096/problem/D 题意 给一个串s,删掉一个字符的代价为a[i],问使得s的子串不含"hard"的最小代价 ...

  3. map、set 使用方法 | 1022 图书馆信息查询

    看了答案才知道了这题的各种骚操作,然后敲了一顿骚键盘,然后wa.调了很久,才发现要规格化打印……mdzz…… 注:加粗代码为傻逼规格化打印代码: #include <stdio.h> #i ...

  4. [POI2014]RAJ(最短路,拓扑排序)

    对于一个点 \(x\) 如何求答案? 由于这个图是个有向无环图,可以先拓扑排序一遍,求出每个点的拓扑序,从起点到它的最长路 \(d2\),从它到终点的最长路 \(d1\).(我写代码是这么写的,注意顺 ...

  5. [LeetCode] 892. Surface Area of 3D Shapes 三维物体的表面积

    On a N * N grid, we place some 1 * 1 * 1 cubes. Each value v = grid[i][j] represents a tower of v cu ...

  6. [LeetCode] 416. Partition Equal Subset Sum 相同子集和分割

    Given a non-empty array containing only positive integers, find if the array can be partitioned into ...

  7. [LeetCode] 415. Add Strings 字符串相加

    Given two non-negative numbers num1 and num2 represented as string, return the sum of num1 and num2. ...

  8. 集合类源码(六)Map(HashMap, Hashtable, LinkedHashMap, WeakHashMap)

    HashMap 内部结构 内部是一个Node数组,每个Node都是链表的头,当链表的大小达到8之后链表转变成红黑树. put操作 final V putVal(int hash, K key, V v ...

  9. docker安装mysql8

    docker run --restart=always -d -v /opt/data/conf.d/:/etc/mysql/conf.d/ -v /opt/data/mysql/:/var/lib/ ...

  10. Flink基本的API(续)

    上一篇介绍了编写 Flink 程序的基本步骤,以及一些常见 API,如:map.filter.keyBy 等,重点介绍了 keyBy 方法.本篇将继续介绍 Flink 中常用的 API,主要内容为 指 ...