rocky191

rocky191 查看完整档案

北京编辑内蒙古民族大学  |  计算机科学与技术 编辑北京xxxx公司  |  前端工程师 编辑 rocky-191.github.io/ 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

rocky191 关注了专栏 · 9月19日

Jerry Wang的SAP技术专栏

SAP成都研究院开发专家,SAP社区导师,SAP中国技术大使

关注 43

rocky191 发布了文章 · 9月5日

Promise链式调用特性总结

相信各位前端小伙伴对于Promise应该很熟悉了吧,日常工作中,100%会用到的一个东西,除非你还在用callback解决异步,那我就太佩服了。话不多说,进入正题。

提前声明一下,以下代码在node环境中实现,你可以创建一个文件,使用nodemon这个工具执行这个文件,就可以进行监听更新,真香。

首先你要创建一个promise

let p=new Promise((resolve,reject)=>{
  resolve('first resolve')
})

方式一、通过return传递结果

p.then(res=>{
  return res;
}).then(res=>{
  console.log(res)
})

控制台就会输出:first resolve

方式二、通过返回新的promise resolve结果

p.then(res=>{
  return res;
}).then(res=>{
  return new Promise((resolve,reject)=>{
    resolve('second resolve: '+res)
  })
}).then(res=>{
  console.log(res)
})

控制台就会输出:second resolve: first resolve
如果在返回的promise里加一个异步比如settimeout呢,结果会是什么样?

p.then(res=>{
  return res;
}).then(res=>{
  return new Promise((resolve,reject)=>{
    // resolve('second resolve: '+res)
    setTimeout(()=>{
      resolve('second resolve: '+res)
    },2000)
  })
}).then(res=>{
  console.log(res)
})

控制台等待2s后输出:second resolve: first resolve

方式三、通过返回新的promise reject 原因

既然可以通过新的promise resolve,那么reject应该也可以。

p.then(res=>{
  return res;
}).then(res=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      reject('error')
    },2000)
  })
}).then(res=>{
  console.log(res)
},err=>{
  console.log('error: '+err)
})

控制台等待2s后输出:error: error

方式四、then函数走了失败回调继续走then

紧接着上一步,失败后,reject出原因,继续后面then

p.then(res=>{
  return res;
}).then(res=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      reject('error')
    },2000)
  })
}).then(res=>{
  console.log(res)
},err=>{
  console.log('error: '+err)
  // 默认 return undefined
}).then(res=>{
  console.log('second then success: '+res)
},err=>{
  console.log('second then error: '+err)
})

控制台会输出两行内容:error: error,second then success: undefined。这就表明在reject 后面继续then会执行下一步的resolve,如果上一步没有返回值,默认接收undefined。

方式五、then中使用throw new Error情况

如果在then中抛出异常呢,如何显示?

p.then(res=>{
  return res;
}).then(res=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      reject('error')
    },2000)
  })
}).then(res=>{
  console.log(res)
},err=>{
  console.log('error: '+err)
}).then(res=>{
  throw new Error('happend error')
}).then(res=>{
  console.log('third then success'+res)
},err=>{
  console.log('third then error '+err)
})

控制台会输出:
error: error
third then error Error: happend error
这表明throw error抛出异常类似reject,会由下一步的then方法中的错误方法处理。

方式六、在promise中使用catch进行错误捕获

p.then(res=>{
  return res;
}).then(res=>{
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      reject('error')
    },2000)
  })
}).then(res=>{
  console.log(res)
},err=>{
  console.log('error: '+err)
}).then(res=>{
  throw new Error('happend error')
}).then(res=>{
  console.log('third then success'+res)
}).catch(err=>{
  console.log('catched '+err)
})

控制台会输出:
error: error
catched Error: happend error

如果在catch方法的前面then中有对上一步错误的处理办法会怎么样呢?

p.then(res=>{
  return res;
}).then(res=>{
  return new Promise((resolve,reject)=>{
    // resolve('second resolve: '+res)
    setTimeout(()=>{
      reject('error')
    },2000)
  })
}).then(res=>{
  console.log(res)
},err=>{
  console.log('error: '+err)
}).then(res=>{
  throw new Error('happend error')
}).then(res=>{
  console.log('third then success'+res)
},err=>{
  console.log('third then error '+ err)
}).catch(err=>{
  console.log('catched '+err)
})

控制台会输出:
error: error
third then error Error: happend error
这说明catch捕获,如果catch前面有error处理函数,catch不会捕获异常的。

如果在catch后面继续then呢?

p.then(res=>{
  return res;
}).then(res=>{
  return new Promise((resolve,reject)=>{
    // resolve('second resolve: '+res)
    setTimeout(()=>{
      reject('error')
    },2000)
  })
}).then(res=>{
  console.log(res)
},err=>{
  console.log('error: '+err)
}).then(res=>{
  throw new Error('happend error')
}).then(res=>{
  console.log('third then success'+res)
}).catch(err=>{
  console.log('catched '+err)
  return 'catched error'
}).then(res=>{
  console.log('catched then '+res)
})

控制台会输出:
error: error
catched Error: happend error
catched then catched error
这说明catch后面是可以继续调用then的,catch 在promise的源码里面其实也是一个then,catch遵循then的运行规则。

总结

promise链式调用,具体是失败还是成功,取决于以下情况:

成功的条件

  • then return 一个普通的js 值
  • then return 一个新的promise成功态的结果 resolve处理

失败的条件

  • then return 一个新的promise失败态的原因 error
  • then throw抛出异常

以上就是promise链式调用的一些实践总结,复习复习基础知识。欢迎大家交流。

参考资料:

查看原文

赞 2 收藏 2 评论 0

rocky191 关注了用户 · 8月19日

刘小夕 @liuyan666

本人微信公众号: 前端宇宙

写文不易,Star支持一下?

【github】https://github.com/YvetteLau/...

关注 1854

rocky191 赞了文章 · 8月11日

React Fiber 源码解析

图片作者:Artem Sapegin,来源:https://unsplash.com/photos/b...

本文作者:刘鹏

前言

在 React v16.13 版本中,正式推出了实验性的 Concurrent Mode,尤其是提供一种新的机制 Suspense,非常自然地解决了一直以来存在的异步副作用问题。结合前面 v16.8 推出的 Hooks,v16.0 底层架构 Fiber,React 给开发者体验上带来了极大提升以及一定程度上更佳的用户体验。所以,对 React 17,你会有什么期待?

Stack Reconciler 和  Fiber Reconciler

我们知道,Stack Reconciler 是 React v15 及之前版本使用的协调算法。而 React Fiber 则是从 v16 版本开始对 Stack Reconciler 进行的重写,是 v16 版本的核心算法实现。
Stack Reconciler 的实现使用了同步递归模型,该模型依赖于内置堆栈来遍历。React 团队 Andrew 之前有提到:

如果只依赖内置调用堆栈,那么它将一直工作,直到堆栈为空,如果我们可以随意中断调用堆栈并手动操作堆栈帧,这不是很好吗? 这就是 React Fiber 的目标。Fiber 是内置堆栈的重新实现,专门用于 React 组件,可以将一个 fiber 看作是一个虚拟堆栈帧。

正是由于其内置 Stack Reconciler 天生带来的局限性,使得 DOM 更新过程是同步的。也就是说,在虚拟 DOM 的比对过程中,如果发现一个元素实例有更新,则会立即同步执行操作,提交到真实 DOM 的更改。这在动画、布局以及手势等领域,可能会带来非常糟糕的用户体验。因此,为了解决这个问题,React 实现了一个虚拟堆栈帧。实际上,这个所谓的虚拟堆栈帧本质上是建立了多个包含节点和指针的链表数据结构。每一个节点就是一个 fiber 基本单元,这个对象存储了一定的组件相关的数据域信息。而指针的指向,则是串联起整个 fibers 树。重新自定义堆栈带来显而易见的优点是,可以将堆栈保留在内存中,在需要执行的时候执行它们,这使得暂停遍历和停止堆栈递归成为可能。

Fiber 的主要目标是实现虚拟 DOM 的增量渲染,能够将渲染工作拆分成块并将其分散到多个帧的能力。在新的更新到来时,能够暂停、中止和复用工作,能为不同类型的更新分配优先级顺序的能力。理解 React 运行机制对我们更好理解它的设计思想以及后续版本新增特性,比如 v17 版本可能带来的异步渲染能力,相信会有很好的帮助。本文基于 React v16.8.6 版本源码,输出一些浅见,希望对你也有帮助,如有不对,还望指正。

基础概念

在了解 React Fiber 架构的实现机制之前,有必要先把几个主要的基础概念抛出来,以便于我们更好地理解。

Work

在 React Reconciliation 过程中出现的各种必须执行计算的活动,比如 state update,props update 或 refs update 等,这些活动我们可以统一称之为 work。

Fiber 对象

文件位置:packages/react-reconciler/src/ReactFiber.js

每一个 React 元素对应一个 fiber 对象,一个 fiber 对象通常是表征 work 的一个基本单元。fiber 对象有几个属性,这些属性指向其他 fiber 对象。

  • child: 对应于父 fiber 节点的子 fiber
  • sibling: 对应于 fiber 节点的同类兄弟节点
  • return: 对应于子 fiber 节点的父节点

因此 fibers 可以理解为是一个包含 React 元素上下文信息的数据域节点,以及由 child, sibling 和 return 等指针域构成的链表结构。

fiber 对象主要的属性如下所示:

Fiber = {
    // 标识 fiber 类型的标签,详情参看下述 WorkTag
    tag: WorkTag,

    // 指向父节点
    return: Fiber | null,

    // 指向子节点
    child: Fiber | null,

    // 指向兄弟节点
    sibling: Fiber | null,

    // 在开始执行时设置 props 值
    pendingProps: any,

    // 在结束时设置的 props 值
    memoizedProps: any,

    // 当前 state
    memoizedState: any,

    // Effect 类型,详情查看以下 effectTag
    effectTag: SideEffectTag,

    // effect 节点指针,指向下一个 effect
    nextEffect: Fiber | null,

    // effect list 是单向链表,第一个 effect
    firstEffect: Fiber | null,

    // effect list 是单向链表,最后一个 effect
    lastEffect: Fiber | null,

    // work 的过期时间,可用于标识一个 work 优先级顺序
    expirationTime: ExpirationTime,
};

从 React 元素创建一个 fiber 对象

文件位置:react-reconciler/src/ReactFiber.js
export function createFiberFromElement(
    element: ReactElement,
    mode: TypeOfMode,
    expirationTime: ExpirationTime
): Fiber {
    const fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, expirationTime);
    return fiber;
}

workTag

文件位置:shared/ReactWorkTags.js

上述 fiber 对象的 tag 属性值,称作 workTag,用于标识一个 React 元素的类型,如下所示:

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedSuspenseComponent = 18;
export const EventComponent = 19;
export const EventTarget = 20;
export const SuspenseListComponent = 21;

EffectTag

文件位置:shared/ReactSideEffectTags.js

上述 fiber 对象的 effectTag 属性值,每一个 fiber 节点都有一个和它相关联的 effectTag 值。
我们把不能在 render 阶段完成的一些 work 称之为副作用,React 罗列了可能存在的各类副作用,如下所示:

export const NoEffect = /*              */ 0b000000000000;
export const PerformedWork = /*         */ 0b000000000001;

export const Placement = /*             */ 0b000000000010;
export const Update = /*                */ 0b000000000100;
export const PlacementAndUpdate = /*    */ 0b000000000110;
export const Deletion = /*              */ 0b000000001000;
export const ContentReset = /*          */ 0b000000010000;
export const Callback = /*              */ 0b000000100000;
export const DidCapture = /*            */ 0b000001000000;
export const Ref = /*                   */ 0b000010000000;
export const Snapshot = /*              */ 0b000100000000;
export const Passive = /*               */ 0b001000000000;

export const LifecycleEffectMask = /*   */ 0b001110100100;
export const HostEffectMask = /*        */ 0b001111111111;

export const Incomplete = /*            */ 0b010000000000;
export const ShouldCapture = /*         */ 0b100000000000;

Reconciliation 和 Scheduling

协调(Reconciliation):
简而言之,根据 diff 算法来比较虚拟 DOM,从而可以确认哪些部分的 React 元素需要更改。

调度(Scheduling):
可以简单理解为是一个确定在什么时候执行 work 的过程。

Render 阶段和 Commit 阶段

相信很多同学都看过这张图,这是 React 团队作者 Dan Abramov 画的一张生命周期阶段图,详情点击查看。他把 React 的生命周期主要分为两个阶段:render 阶段和 commit 阶段。其中 commit 阶段又可以细分为 pre-commit 阶段和 commit 阶段,如下图所示:

image.png

从 v16.3 版本开始,在 render 阶段,以下几个生命周期被认为是不安全的,它们将在未来的版本中被移除,可以看到这些生命周期在上图中未被包括进去,如下所示:

  • [UNSAFE_]componentWillMount (deprecated)
  • [UNSAFE_]componentWillReceiveProps (deprecated)
  • [UNSAFE_]componentWillUpdate (deprecated)

在 React 官网中明确提到了废弃的原因,这些被标记为不安全的生命周期由于常常被开发者错误理解甚至被滥用,比如一些开发人员会倾向于将带有请求数据等副作用的逻辑放在这些生命周期方法中,认为能带来更好的性能,而实际上真正带来的收益几乎可以忽略。在未来, React 逐步推崇异步渲染模式下,这很有可能会因为不兼容而带来很多问题。

在 render 阶段,React 可以根据当前可用的时间片处理一个或多个 fiber 节点,并且得益于 fiber 对象中存储的元素上下文信息以及指针域构成的链表结构,使其能够将执行到一半的工作保存在内存的链表中。当 React 停止并完成保存的工作后,让出时间片去处理一些其他优先级更高的事情。之后,在重新获取到可用的时间片后,它能够根据之前保存在内存的上下文信息通过快速遍历的方式找到停止的 fiber 节点并继续工作。由于在此阶段执行的工作并不会导致任何用户可见的更改,因为并没有被提交到真实的 DOM。所以,我们说是 fiber 让调度能够实现暂停、中止以及重新开始等增量渲染的能力。相反,在 commit 阶段,work 执行总是同步的,这是因为在此阶段执行的工作将导致用户可见的更改。这就是为什么在 commit 阶段, React 需要一次性提交并完成这些工作的原因。

Current 树和 WorkInProgress 树

首次渲染之后,React 会生成一个对应于 UI 渲染的 fiber 树,称之为 current 树。实际上,React 在调用生命周期钩子函数时就是通过判断是否存在 current 来区分何时执行 componentDidMount 和 componentDidUpdate。当 React 遍历 current 树时,它会为每一个存在的 fiber 节点创建了一个替代节点,这些节点构成一个 workInProgress 树。后续所有发生 work 的地方都是在 workInProgress 树中执行,如果该树还未创建,则会创建一个 current 树的副本,作为 workInProgress 树。当 workInProgress 树被提交后将会在 commit 阶段的某一子阶段被替换成为 current 树。

这里增加两个树的主要原因是为了避免更新的丢失。比如,如果我们只增加更新到 workInProgress 树,当 workInProgress 树通过从 current 树中克隆而重新开始时,一些更新可能会丢失。同样的,如果我们只增加更新到 current 树,当 workInProgress 树被提交后会被替换为 current 树,更新也会被丢失。通过在两个队列都保持更新,可以确保更新始终是下一个 workInProgress 树的一部分。并且,因为 workInProgress 树被提交成为 current 树,并不会出现相同的更新而被重复应用两次的情况。

Effects list

effect list 可以理解为是一个存储 effectTag 副作用列表容器。它是由 fiber 节点和指针 nextEffect 构成的单链表结构,这其中还包括第一个节点 firstEffect,和最后一个节点 lastEffect。如下图所示:

image.png

React 采用深度优先搜索算法,在 render 阶段遍历 fiber 树时,把每一个有副作用的 fiber 筛选出来,最后构建生成一个只带副作用的 effect list 链表。
在 commit 阶段,React 拿到 effect list 数据后,通过遍历 effect list,并根据每一个 effect 节点的 effectTag 类型,从而对相应的 DOM 树执行更改。

更多 effect list 构建演示流程,可以点击查看动画 《Effect List —— 又一个 Fiber 链表的构建过程》

Render 阶段

在本文中,我们以类组件为例,假设已经开始调用了一个 setState 方法。

enqueueSetState

每个 React 组件都有一个相关联的 updater,作为组件层和核心库之间的桥梁。
react.Component 本质上就是一个函数,在它的原型对象上挂载了 setState 方法

文件位置:react/src/ReactBaseClasses.js
// Component函数
function Component(props, context, updater) {
    this.props = props;
    this.context = context;
    this.updater = updater || ReactNoopUpdateQueue;
}

// Component原型对象挂载 setState
Component.prototype.setState = function (partialState, callback) {
    this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

React 给 work 大致分成以下几种优先级类型,其中 immediate 比较特殊,它的优先级最高,可以理解为是同步调度,调度过程中不会被中断。

export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;

React 有一套计算逻辑,根据不同的优先级类型为不同的 work 计算出一个过期时间 expirationTime,其实就是一个时间戳。所谓的 React 在新的更新到来时,能为不同类型的更新分配优先级顺序的能力,本质上是根据过期时间 expirationTime 的大小来确定优先级顺序,expirationTime 数值越小,则优先级越高。在相差一定时间范围内的 work,React 会认为它们是同一个批次(batch)的,因此这一批次的 work 会在一次更新中完成。

文件位置:react-reconciler/src/ReactFiberClassComponent.js
const classComponentUpdater = {
    enqueueSetState(inst, payload, callback) {
        // 获取 fiber 对象
        const fiber = getInstance(inst);
        const currentTime = requestCurrentTime();

        // 计算到期时间 expirationTime
        const expirationTime = computeExpirationForFiber(currentTime, fiber, suspenseConfig);

        const update = createUpdate(expirationTime, suspenseConfig);
        // 插入 update 到队列
        enqueueUpdate(fiber, update);
        // 调度 work 方法
        scheduleWork(fiber, expirationTime);
    },
};

renderRoot

文件位置:react-reconciler/src/ReactFiberWorkLoop.js

协调过程总是 renderRoot 开始,方法调用栈:scheduleWork -->  scheduleCallbackForRoot  --> renderRoot

代码如下:

function renderRoot(
  root: FiberRoot,
  expirationTime: ExpirationTime,
  isSync: boolean,
) | null {
  do {
    // 优先级最高,走同步分支
    if (isSync) {
      workLoopSync();
    } else {
      workLoop();
    }
  } while (true);
}

// 所有的fiber节点都在workLoop 中被处理
function workLoop() {
  while (workInProgress !== null && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

performUnitOfWork

所有的 fiber 节点都在 workLoop 方法处理。协调过程总是从最顶层的 hostRoot 节点开始进行 workInProgress 树的遍历。但是,React 会跳过已经处理过的 fiber 节点,直到找到还未完成工作的节点。例如,如果在组件树的深处调用 setState,React 将从顶部开始,但会快速跳过父节点,直到到达调用了 setState 方法的组件。整个过程采用的是深度优先搜索算法,处理完当前 fiber 节点后,workInProgress 将包含对树中下一个 fiber 节点的引用,如果下一个节点为 null 不存在,则认为执行结束退出 workLoop 循环并准备进行一次提交更改。

方法调用栈如下:
performUnitOfWork  -->  beginWork -->  updateClassComponent --> finishedComponent --> completeUnitOfWork

代码如下所示:

文件位置:react-reconciler/src/ReactFiberWorkLoop.js
function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
    const current = unitOfWork.alternate;

    let next;
    next = beginWork(current, unitOfWork, renderExpirationTime);

    // 如果没有新的 work,则认为已完成当前工作
    if (next === null) {
        next = completeUnitOfWork(unitOfWork);
    }

    return next;
}

了解树的深度优先搜索算法,可点击参考该示例 《js-ntqfill》

completeUnitOfWork

文件位置:react-reconciler/src/completeUnitOfWork.js

在 completeUnitOfWork 方法中构建 effect-list 链表,该 effect list 在下一个 commit 阶段非常重要,关于 effect list 上述有介绍。

如下所示:

function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
    // 深度优先搜索算法
    workInProgress = unitOfWork;
    do {
        const current = workInProgress.alternate;
        const returnFiber = workInProgress.return;

        /*
        构建 effect-list部分
    */
        if (returnFiber.firstEffect === null) {
            returnFiber.firstEffect = workInProgress.firstEffect;
        }
        if (workInProgress.lastEffect !== null) {
            if (returnFiber.lastEffect !== null) {
                returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
            }
            returnFiber.lastEffect = workInProgress.lastEffect;
        }

        if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = workInProgress;
        } else {
            returnFiber.firstEffect = workInProgress;
        }
        returnFiber.lastEffect = workInProgress;

        const siblingFiber = workInProgress.sibling;
        if (siblingFiber !== null) {
            // If there is more work to do in this returnFiber, do that next.
            return siblingFiber;
        }
        // Otherwise, return to the parent
        workInProgress = returnFiber;
    } while (workInProgress !== null);
}

至此,一个 render 阶段大概流程结束。

Commit 阶段

commit 阶段是 React 更新真实 DOM 并调用 pre-commit phase 和 commit phase 生命周期方法的地方。与 render 阶段不同,commit 阶段的执行始终是同步的,它将依赖上一个 render 阶段构建的 effect list 链表来完成。

commitRootImpl

commit 阶段实质上被分为如下三个子阶段:

  • before mutation
  • mutation phase
  • layout phase

mutation 阶段主要做的事情是遍历 effect-list 列表,拿到每一个 effect 存储的信息,根据副作用类型 effectTag 执行相应的处理并提交更新到真正的 DOM。所有的 mutation effects 都会在 layout phase 阶段之前被处理。当该阶段执行结束时,workInProgress 树会被替换成 current 树。因此在 mutation phase 阶段之前的子阶段 before mutation,是调用 getSnapshotBeforeUpdate 生命周期的地方。在 before mutation 这个阶段,真正的 DOM 还没有被变更。最后一个子阶段是 layout phase,在这个阶段生命周期 componentDidMount/Update 被执行。

文件位置:react-reconciler/src/ReactFiberWorkLoop.js

如下所示:

function commitRootImpl(root) {
    if (firstEffect !== null) {
        // before mutation 阶段,遍历 effect list
        do {
            try {
                commitBeforeMutationEffects();
            } catch (error) {
                nextEffect = nextEffect.nextEffect;
            }
        } while (nextEffect !== null);

        // the mutation phase 阶段,遍历 effect list
        nextEffect = firstEffect;
        do {
            try {
                commitMutationEffects();
            } catch (error) {
                nextEffect = nextEffect.nextEffect;
            }
        } while (nextEffect !== null);

        // 将 work-in-progress 树替换为 current 树
        root.current = finishedWork;

        // layout phase 阶段,遍历 effect list
        nextEffect = firstEffect;
        do {
            try {
                commitLayoutEffects(root, expirationTime);
            } catch (error) {
                captureCommitPhaseError(nextEffect, error);
                nextEffect = nextEffect.nextEffect;
            }
        } while (nextEffect !== null);

        nextEffect = null;
    } else {
        // No effects.
        root.current = finishedWork;
    }
}

commitBeforeMutationEffects

before mutation 调用链路:commitRootImpl -->  commitBeforeMutationEffects --> commitBeforeMutationLifeCycles

代码如下:

function commitBeforeMutationLifeCycles(
  current: Fiber | null,
  finishedWork: Fiber,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
    ...
    // 属性 stateNode 表示对应组件的实例
    // 在这里 class 组件实例执行 instance.getSnapshotBeforeUpdate()
    case ClassComponent: {
      if (finishedWork.effectTag & Snapshot) {
        if (current !== null) {
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          const instance = finishedWork.stateNode;
          const snapshot = instance.getSnapshotBeforeUpdate(
            finishedWork.elementType === finishedWork.type
              ? prevProps
              : resolveDefaultProps(finishedWork.type, prevProps),
            prevState,
          );

          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
        }
      }
      return;
    }
    case HostRoot:
    case HostComponent:
    case HostText:
    case HostPortal:
    case IncompleteClassComponent:
      ...
  }
}

commitMutationEffects

文件位置:react-reconciler/src/ReactFiberWorkLoop.js

mutation phase 阶段调用链路:
commitRootImpl -->  commitMutationEffects --> commitWork

代码如下:

function commitMutationEffects() {
  while (nextEffect !== null) {
    const effectTag = nextEffect.effectTag;

    let primaryEffectTag = effectTag & (Placement | Update | Deletion);
    switch (primaryEffectTag) {
      case Placement:
        ...
      case PlacementAndUpdate:
        ...
      case Update: {
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      case Deletion: {
        commitDeletion(nextEffect);
        break;
      }
    }
  }
}

commitLayoutEffects

文件位置:react-reconciler/src/ReactFiberCommitWork.js

layout phase 调用链路:commitRootImpl -->  commitLayoutEffects --> commitLifeCycles

代码如下:

function commitLifeCycles(
  finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedExpirationTime: ExpirationTime,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
      ...
    case ClassComponent: {
      // 属性 stateNode 表示对应组件的实例
      // 在这里 class 组件实例执行 componentDidMount/DidUpdate
      const instance = finishedWork.stateNode;
      if (finishedWork.effectTag & Update) {
        // 首次渲染时,还没有 current 树
        if (current === null) {
          instance.componentDidMount();
        } else {
          const prevProps =
            finishedWork.elementType === finishedWork.type
              ? current.memoizedProps
              : resolveDefaultProps(finishedWork.type, current.memoizedProps);
          const prevState = current.memoizedState;
          instance.componentDidUpdate(
            prevProps,
            prevState,
            instance.__reactInternalSnapshotBeforeUpdate,
          );
        }
      }
      const updateQueue = finishedWork.updateQueue;
      if (updateQueue !== null) {
        commitUpdateQueue(
          finishedWork,
          updateQueue,
          instance,
          committedExpirationTime,
        );
      }
      return;
    }
    case HostRoot:
    case HostComponent:
    case HostText:
    case HostPortal:
    case Profiler:
    case SuspenseComponent:
    case SuspenseListComponent:
      ...
  }
}

扩展

以下是一些关于 Fiber 的扩展内容。

调用链路

如下图所示,根据 React 源码绘制的调用链路图,主要罗列了一些比较重要的函数方法,可作为大家了解 Fiber 的参考。源码调试过程可以找到对应的函数方法打断点,以了解实际运行的过程,便于更好梳理出各个逻辑方法之间的关系。

fiber调用链路.jpg

requestIdleCallback

之前有文章在总结 React Fiber 的调度原理时提到,客户端线程执行任务时会以帧的形式划分,在两个执行帧之间,主线程通常会有一小段空闲时间,在这个空闲期触发 requestIdleCallback 方法,能够执行一些优先级较低的 work。

据说在早期的 React 版本上确实是这么做的,但使用 requestIdleCallback 实际上有一些限制,执行频次不足,以致于无法实现流畅的 UI 渲染,扩展性差。因此 React 团队放弃了 requestIdleCallback 用法,实现了自定义的版本。比如,在发布 v16.10 版本中,推出实验性的 Scheduler,尝试使用 postMessage 来代替 requestAnimationFrame。更多了解可以查看 React 源码 packages/scheduler 部分。

小结

Fiber 由来已久,可以说是 React 设计思想的一个典型表现。相比业界其他流行库更多采用当新数据到达时再计算模式,React 坚持拉取模式,即能够把计算资源延迟到必要时候再用,并且它知道,什么时候更适合执行,什么时候不执行。看起来虽然只是微小的区别,却意义很大。随着后续异步渲染能力等新特性的推出,我们有理由相信,在未来,React 将会在人机交互的应用中给我们带来更多的惊喜。

参考

本文发布自 网易云音乐大前端团队,文章未经授权禁止任何形式的转载。我们常年招收前端、iOS、Android,如果你准备换工作,又恰好喜欢云音乐,那就加入我们 grp.music-fe(at)corp.netease.com!
查看原文

赞 12 收藏 5 评论 1

rocky191 关注了专栏 · 8月11日

网易云音乐大前端团队

网易云音乐大前端技术团队专栏

关注 1616

rocky191 赞了文章 · 7月12日

技术人如何自我成长?

7月9日 19:00-21:30 阿里云开发者社区首场“Offer 5000”直播开启!15位团队技术大牛在线招人,更有《阿里云技术面试红宝书》助你拿下Offer!
图片无法显
点击图片或戳我查看详情和投简历


1.png

作者 | 箫逸  阿里文娱高级技术专家

导读:转眼 2020 已经过去了一半,是时候来做一次年中总结了。本文中,阿里文娱高级技术专家箫逸总结了自己在阿里 6 年来的成长和收获,分享他在工作中的一些思维方法,以及对生活的一些感悟,希望对同学们有所启发。

观察自己的成长,才能收获更大的成长

很长时间以来我基本都是基于天性或本能在做事情,不是说没有思考,是从来没有思考过自己为什么那么思考。过去一年最大的觉悟是逐渐向内看,自己怎么做的,怎么思考的,怎么成长的,自己为什么是那么想的,为什么是那样做的,抓住思维过程,让更多的天性或本能暴露,向内审视自己做事情的逻辑,从而建立起自己的体系去思考,去想问题。在这个过程中逐渐地形成了自己在成长过程中所遵循的一系列“章法”,我想这是我这一年来最大的成长。

1. 从孩子身上重新学习

闺女 1 岁半,逐渐可以认识挂在墙上的各种水果卡片,虽然她不会说,但是当我问起诸如苹果在哪儿,豌豆射手在哪儿之类的问题时,她总能快速指到正确的位置。我欣喜于这种快速的成长,不禁想她到底是怎么学会的?有意识的观察结果发现,她始终坚持了一个最简单的法则:模仿 -> 重复 -> 学会 -> 学会下一个,她的成长过程始终坚持着这样一个小循环,这种循环在不断地扩大她的认知圈。

2.png

上边这几张图按顺序可以代表我观察孩子成长过程获得的深刻启发。最左边是一个微小的循环,其中的关键在于“知道如何学会”,这个过程与刻意练习的过程近乎类似,但是更加聚焦于微小且具体的事或者哪怕一丁点儿的进步,这个小循环的价值在于审视、觉察、反思和巩固,可以极大的带来学习的动力。我把这个循环实践到带领儿子的各科学习过程中,通过反复的确认“如何做到的”完成对他成长的鼓励,小家伙面对困难的信心也是越来越足。右侧的三幅图是层次逐步上升的表述,从刻意练习到认知的边界,再到更长时间维度上的螺旋式成长,基本组成了成长中必然经历的过程。

这其中最难的是什么?我认为是对循环过程的坚持和及时止损。

我们都知道复利的指数分布图,在最开始的很长一段时间内趋势趋于平滑,需要很长的时间才能迎来拐点,这其中需要坚持,因为只有坚持才能借助时间的催化作用产生复利。我认为坚持训练“知道如何学会”这个思维卡点,坚持通过刻意练习强化某一件事情、某一类技能或某个概念的心理表征,这是让拐点快速到来唯一的途径。除此之外,要克服内心的焦虑感,让自己慢下来(请注意,慢不是速度问题,而是标准问题)。

但不是一直坚持就可以迎来拐点,这里还有另外一层,就是要“及时止损”,巴菲特说他的投资原则:Rule No 1,Never lose money;Rule No 2,Never forget rule No.1。乍听起来第二条像是废话,但仔细理解背后其实是“及时止损”这层深意:必须保证一寸一寸的前进,必须在乎一寸一寸的得失。只有这样舒适区才能不断的扩大。我们可以在很短时间内掌握一个技能或一门知识,但是如果没有输出,没有实践,就会很快失去这个收获,因为遗忘是天性。而及时止损的本质是对抗遗忘,对抗遗忘的最好办法是不断的重复,不断的使用,不断的实践。《认知天性》这本书里提到关于记忆神经,通过检索形成突触,为记忆打一个结。这个结就如同我们在健身房不断的训练,刺激,不断的撕裂肌肉神经让他重新再生长一样,也是一个长肌肉的过程,所以要想成长必须学会“及时止损”。

这是我去年从孩子身上获得的学习收获,我一直在不断的敦促自己完成单个小闭环,不断的重复,不断的进化,期望能够快速迎接到拐点,从而走出完整的复利曲线。

2. 一切兼有章法

万维钢在《万万没想到》一书中说到:人所掌握的知识和技能绝非是零散的信息和随意的动作,他们大多具有某种 “结构”,这些结构就是模型。而厉害的人,或者精英就是善于掌握和利用这些模型解决问题的人。我理解这里的模型就是“章法”,也可以理解为我们常说的套路。

延展到过去一年在团队和管理上的成长,我从很多的高年级同学的分享中汲取到了章法的力量。一切的模型皆为抽象,这个点和技术领域里的抽象是相通的,比如去年在团队中曾分享过《如何画好一张架构图》就是一种章法的总结;再比如绩效管理这个周期性的管理动作,被抽象为定目标-追过程-拿结果这样的模型。一旦形成章法,并且基于章法再去展开思考,会让整个过程达到事半功倍的效果。

那么如何找到“章法”呢?我的经验是时刻询问自己:我是如何学会的?觉察过程,基于结果审视,复盘思考沉淀,认知实践,反复确认再认知,我觉得这就是形成章法的关键所在。这个章法当然可以参考别人的,但是我觉得自己的思考才是最最有用的。一切兼有章法,只要肯用心、肯下苦功夫,敢于在思想上艰苦奋斗,我相信就一定会有大的收获。

过去的一年,团队在管理实践上扎扎实实的思考、落地,通过核心团队的一次又一次复盘,思考,讨论,强化了执行次数,形成了符合复利的驱动模型。把做一件事情的“章法”反复作用于学习-坚持-实践的上升螺旋,那么在这件事情上,就可以收获比别人或比自己过去快的多的成长,这是过去一年我在管理成长上的最大收获。

3. 认真生活,快乐工作

去年 9 月份团队有一次普吉岛 Outing,Outing 的时候我发现很多平时工作中表现很低调的同学,在 Outing 中充满了闪光点。我突然发现,我们每一个同学都是一座冰山,在工作中表现出来的只是冰山可见的那部分,而冰山之下的日常生活才更能真实全面的反应一个人,这给了我非常大的启发,让我更加深入的思考“认真生活,快乐工作”背后的智慧。

我觉得认真生活必须要学会欣赏,要谦卑的去欣赏接触到的每一个人,发现别人身上的闪光点,这是我 Outing 中最大的收获。这种欣赏让我越来越包容,能够包容和接受自己从前无法接受的人或事,也更加向内看自己。这种包容,也让自己的内心就越来越开放,也越能接收不一样的观点,能获得很多从前属于“盲区”的成长体验。这个过程需要修炼,需要保持内心的谦卑,一个人在工作上的魅力可能全无,但在生活中他是一个非常闪光的人,保持谦卑可以让自己看到一个完整的人。

认真生活也是一种态度,更是对人生的一种理解。在过去,我一直觉得自己处在一个奋斗的年纪,从来没有过多的关注生活,我也从来没有相信什么生活和工作的平衡,我对待工作的热情和投入的精力要远大过于我的生活。我觉得人的精力是有限的,必须要把最旺盛的阶段投入到事业和个人能力的提升上来。所以对待“认真生活”从来没有仔细去想过,更多停留在喊喊说说而已,我狭隘的看待了生活与工作之间的关系,Outing 之后我有不一样的思考。

我们常常会提到一个问题:如何平衡生活和工作本身?这里边有一个词叫“平衡”,我不知道这个词在大家脑海里的画像是什么?在我脑子里第一出现的是“天平”,要么投入生活,工作上不做过多追求;要么更多思考工作,生活更多服务于工作。这种工作和生活非此即彼的狭义理解其实深刻阻碍了自己进一步的思考。我很长时间都是这么割裂开来看的。事实是工作是生活的一部分,是包含关系,生活是什么?生活是人生,生活包含了事业(工作)、家庭(我们常常理解的生活)、孩子、教育、兴趣爱好等等。《高效能人士的七个习惯》这本书在讲“要事第一”时提到“木桶人生”,这个木桶里可以装大石头、小石子、沙子等等,工作对于我可能是一个大石头,它承载了我很多人生的价值、经济基础等底座,这本书让我觉得生活是可以全面推进的,核心是我们需要识别自己在生活中每一个角色里承担的“重要不紧急”事项,然后做好它,做好这关键的 20%,不断的汲取,以“全面推进”为原则,而不是以某一个片面的侧重点为方向。而且日常生活中的思考和启发其实是可以很好的作用于工作。发现美好,发现痛的地方,发现人性中趋利避害,这是一种智慧,这也是过去一年在认真生活里收获的成长,我还在不断的强化这种理解,不断的在践行这条价值观。

支撑我不断前进的底层逻辑

上面的文字更多是过去一年汲取的成长,下面这些内容是我回顾六年走来的感悟,也算作这六年的一个总结。

1. 此时此刻,非我莫属

这是我最喜欢的一句阿里土话,在它成为价值观之前,有太多太多的故事通过这八个字滋养了我。这八个字包含了使命感、责任感、担当、激情、更包含了一种“舍我其谁”的豪气。我很好奇,新六脉三角形中“此时此刻 非我莫属”放在最底层,最中间的部分,是巧合还是有意为之。在我看来这一条是所有其他价值观的内生动力源泉,无论是在个人成长还是在客户价值的捍卫中,都是非常非常关键的。如何保持内生的激情,从我的历史经验看至少有三点。

1)保持理想主义

《终身成长》这本书里提到了两个思维模式:固定型思维模式和成长型思维模式。下图是这两种思维模式的对比,在面对各种情况时两种思维模式有着各不相同的理解,这本书讲到可以不断的通过观察,训练获得改变。这其实也和我们常常提到的理想主义和现实主义区别类似,理想主义的特质就是很傻很天真。这六年来趟过很多坑,也做过很多事儿,感谢一路走来牵引我的人,让我保持天真:对未来充满期待、愿意不断尝试、不抱怨和保持自我激励。

3.png

2)保持乐观

我在阿里的这六年,主动变化 1 次,被动变化 N 次。在过往的经历中,我觉得在变化中最好的办法是保持乐观,不管事情多么复杂,不管事情是一个坑还是一个锅,发现问题,天然会变得很兴奋,内心 OS 是:这里需要我,这是一个成长的机会;面对更大的问题,更大的坑,好,该是我力挽狂澜的时候了。我想就是这样一种内在驱动,这样一种理想主义的单纯,让我坚持走过了 6 年。回顾自己这六年,不是说我自己做的多么好,多么的积极上进,我也曾有过低落的时候,但是内心中始终把乐观作为自己的“均值”,回归“均值”就只是一个时间的问题。在最困难的时候,不妨多读读“土话”,每一句土话背后都是故事,每一句土话都是凝聚智慧,不同的时期,不同的阶段,可能某一句就会让你变得通透,产生出继续的勇气和豪气。

3)赋予工作意义

工作上不可能所有的事情都能按自己的期望来,或多或少总会有一些大家理解的“杂活”、“苦力活”,在一个项目或一段合作中,不妨多问问自己希望被别人记住什么?然后赋予这段工作意义。我记得去年女排世界杯的时候,记者采访郎平,她提到每个人发球要有“使命感”,赋予每一次发球意义,大致意思是:不能说自己怕失误就保守,所以每个人都要去冲击对手,发一个很菜的球自己没有失误的责任,完成了发球的任务,但是对方确可以很容易组织一次进攻给队友带来压力。这可能是我想说的“工作意义”。不管多么小的事情,都能找到自己的“价值感”,把事情做好,让队友更轻松。

2. 对结果负责,因我不同

进入阿里很长一段时间,我对自己的要求是“问题到我即止”,尽可能做到用户反馈的问题或线上告警,只要我看到了或者指派到我这里,我要做到对这个问题的收口,给出最终的原因或结果。这个过程可能会跨越很多团队,可能会跨越整个业务链路,每每我想要放弃的时候,“问题到我即止”这个信念就会跳出来鄙视我。这样一路下来的结果是个人能力在那个阶段有了飞速的成长,个人在团队中的影响力也越来越大。

再到后来,这个自我要求变成了“因我不同”(因为我的参与,能不能让事情变得不一样,能不能变得更好,更高效,亦或是更有章法),经历了飞猪到大麦再到奥运团队,“因我不同”这个信念已经逐渐替换了“问题到我即止”,但是也同样会经常性的跳出来鄙视我。

1)"因我不同"就是要不断提升自己做事情的标准

对结果负责,这是我觉得对“因我不同”最直观解读。这里的结果对应着层次,不同的层次,其实对应着不同的标准。不同标准的背后对应着不同程度的付出。比如日常的开发工作,如果标准是:把我负责的部分做完就可以了,那可能是在个人的结果层面;如果标准是:我自己负责的部分做完了,同时也关注项目最终的目标,确保上下左右的所有合作方一起拿到结果,那可能是在项目的结果层面;如果标准是:我要把这件事情做完、上下游全部串联起来,同时我知道这件事情背后的意义,着手未来整体的规划,更体系化的做事情,那可能是团队或者更高的层面。

一切兼有层次(层次=标准),“因我不同”就是要不断提升自己做事情的标准,标准越高的的人,越能对一件事情有更深刻的认知,久而久之,就形成了能力的区分,也就造就了不一样的成长速度。

2)"因我不同"就是 Make Different,Make Impact,Make Friends

这个 3M 法则不是我的原创,来自于已经离职的一个 HR 姐姐,她说这是过去在阿里的生涯中指导她做事情的 3 大原则,这种解读更具有建设性。充分发挥主观能动性,就要像蜡烛一样,时刻将个人影响力发挥到最大。在任何可能的地方 Make Different,让事情发生微小和美好的改变;从小事儿做起,从点滴做起,从身边的人做起,以身作则,也许就能在部门发挥影响力,也许就能在 BG 产生影响力,也许能在整个集团产生影响力,勇于尝试,勇于 Make Impact;而我们始终要记得工作中我们做很多的事情,最终发现留下来的不是事情本身,而是我们在做事情时候留给别人的印象,别人打给自己的标签,所以 Make Friends,这种印象/标签就是友情储蓄,可以支持我们在你中有我,我中有你的阿里做更多事情。

3. 永远不要"屁股决定脑袋"

这里我想表达两层意思,一层是我们需要“进化”,需要不断的发现和感知世界的变化,更快的适应变化,有时候你在哪儿比你做什么更重要;另外一层是格局和眼界,就是不要被自己所处层级、所在的角色立场、视角所局限。

1)“开天眼”

“开天眼”,我觉得是一个非常形象的词。山坡上的羊比山脚下的羊应对狼来了更具有优势,尽管我们可能还在山脚下,但要对周围发生的事情保持敏锐,要让自己的认知向半山腰处延伸。比如去看各个领域的大牛们感知到技术趋势是什么,变化是什么?逐步让自己去看见,去相信,去塑造。这里其实可以再一次应用到“黑洞”的隐喻,这个世界是立体的,任何事情,思考,想法是有层次的,我们要做的就是不断提升自己的思考层次,不断的提升自己的格局,至少要把自己拔高一到两个层次,去发现和感知世界的变化,只有这样才能保持前进的步伐。

2)仰望星空,脚踏实地

“开天眼”很重要,脚踏实地更重要。如果一味的追求仰望星空,很容易失去焦点,走火入魔。靠谱的做法是结合当下的业务,结合当下团队的挑战,模仿先行者的行动,模仿相同层次或层次相近的先行者,不断的“照镜子”。列几个榜样的人,一个个去看,逐渐的去超越。最开始的时候,不妨从遇到一个问题时,把榜样带入,从“他会怎么想?”这个问题开始,逐步思考为什么?从学会到知道如何学会,再到巩固、沉淀、形成章法,然后推进到下一个榜样。这个过程训练的是格局和眼界,突破自己当下的视角和立场去想问题,脱离当前的屁股做事情。

写在最后

以上文字作为过去一年的总结,也为过去的六年画上一个“逗号”。2020 年,需要在工作、学习、认知、身体等多个层面全面的推进和迭代自己,强化自己的输入输出端,用 OKR 作为工具驱动自己,获取更多的进步。六年前因为吸引而加入,六年来因为梦想而坚持,未来希望能够因为理想而传承。

体验有礼:5 分钟极速上手 Serverless

“Serverless” 近年来非常火爆。人人都热衷于探讨它出现的意义,但对于如何上手使用或在生产环境落地,却谈之甚少。我们设计了体验场景,手把手带你 5 分钟上手 Serverless,还送 2000 个阿里云“第一行代码”鎏金限量马克杯!

点击查看详情https://developer.aliyun.com/adc/series/fc/

阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”
查看原文

赞 3 收藏 1 评论 0

rocky191 关注了专栏 · 6月13日

前端之路

前端知识,它得有个好地方。

关注 96

rocky191 赞了文章 · 5月31日

我没有开挂的人生!自律和坚持,是我走IT之路的唯一捷径

作为一个半路出家的自学Linux系统运维的杭漂,他的这十年,可以说是遍历艰辛。他在大家都认定“运维要被自动化替代”时坚定发声:“这是一个错误的理念,运维人员会一直存在”,并成为了逆行人。

“生命不息,折腾不止”,他舍去十年打拼的事业,创建了公众号——民工哥技术之路,将自己的所学、经验分享出来,这其中的不易,恐怕只有经历过那个资源匮乏、一个人单打独斗的互联网初期时代的IT人才能感同身受。

不仅如此,他还用十分接地气的方式写了一本书——《Linux系统运维指南:从入门到企业实战》,完整而清晰地阐述了企业系统运维的核心知识,从而帮助同行们更快地进入企业运维工程师的角色。

他就是在运维路上初心不改的民工哥。

本文源自人民邮电出版社异步社区对我的专访稿

大家好,我是民工哥。

其实,我想说,我真的没有开挂的人生,也没有传奇般的经历,也算不上什么普通人的逆袭啥的,只有一些自我学习、成长之路的过程。今天,给大家分享一下我的IT从业之路的经历,希望对大家有所帮助、有所借鉴。

“自律和坚持,是我走IT技术之路的唯一捷径。”

1、网瘾少年的自我救赎

我出生在安徽南部的一个小山村里。在上世纪80年代,农村的经济情况都不太好,家家户户都随时有口粮断缺的情况,我的家也不例外。

我的父亲是家里的老大,因此他从小就担起了作为大哥的责任:干农活以及养家糊口。由于常年劳累,我记事的时候,父亲的身体已经累垮了,因此家庭的重担便由瘦弱的母亲独自扛了起来。那时,我印象最深刻的就是父亲咳嗽的声音和母亲扛起重物的样子。

为了让家里的生活不那么拮据,母亲不辞辛苦,将所有苦痛都一个人吞下。我心疼母亲,很小就开始帮母亲干农活,那时我就明白了:只有走出这座大山,我才能让家人过上更好的生活。

我的老家

但中学时期,我被新奇有趣的网络游戏吸引并且一发不可收拾,沉迷游戏的我一时忘记了梦想以及辛苦供我读书的母亲,直到我看见成绩单上那惨不忍睹的数字。除了英语的成绩还过的去以外,其它理科,比如物理满分150分,我却连零头都没达到,我当时一度以为自己会辍学当农民工,就真成了“民工哥”了。

我回想起父母手上的老茧,自己儿时所吃的苦,这一幕幕激励我不要玩物丧志,要“从哪里跌倒,就从哪里爬起来”,既然痴迷电脑游戏,那我干脆就搞出点名堂。

从此,我便同网络和代码结下了不解之缘。

2、杭漂这十年

2007年3月8日,我独自一人拖着一个行李箱到了杭州,租了一个有独立的小厨房、卫生间、加上房间约15平方左右的小房子,找了一个月薪不到1000元的工作,由此开启了我的十年杭漂生涯

07年初杭州西湖

真正开始工作了,我才知道课堂上所学的知识和现实应用有着天壤之别。我虽然顶着网络工程师的头衔,但实际上是做的是企业的网管。这让我意识到了以后的路有多难,但我坚信路上的磕绊会让我成长!

才开始工作没几天,就发生了一件事。

当时一名同事电脑用的比较久,感觉不太好用,希望我帮忙重装下系统。我二话不讲,上手就重装起来(第一次实操),三下五除二搞定了。可是不久后,同事拿着电脑过来对我说:“我电脑桌面的这个位置少了一个图标。”说实话,当时我就吓懵了。

后来才知道,由于每个系统不一样,装完系统后默认的桌面图标也是不相同的,因为这事,这个同事纠缠了我好久好久。也正是因为如此,吃一堑长一智,到现在还我保留着那个因“祸”得来的好习惯,无论什么情况下,备份第一,备份第一,备份第一。

接下来的两年里,我陆陆续续做过很多岗位的工作,网管、网工、企业信息化建设、类似PM的岗位、信息技术经理、信息安全等。像大多数杭漂一样,我想在这座繁华又冷漠的城市里努力谋生并有自己的一席之地。

之后,我开始产生专注于运维岗位的想法。凭着自己对网络设备的浓厚兴趣,我开始了自学运维之路。那段自学经历很艰辛,那时互联网没有这么发达,查找资料也没有现在这么方便,素材也不是很丰富,很多时候只有靠自己看官方的资料去慢慢摸索,有时候几小时、几天可能都难以解决一个错误,只有通过不断地总结和反复练习,才能掌握一个个知识点。

那时,我养成了用笔记记录当天遇到的问题、思考过程和解决方法的习惯,光是备考CCIE RS的笔记,我就写了长达20万+字。尽管经济拮据,尽管前程未卜,我依然坚信学习是最好的投资”。

那段经历也让我明白了,人活着必须要有一定的目标,但并非遥不可及的目标,而是完全能靠自己一步步努力达成的目标。每天进步一点点,每年完成目标的一部分,哪怕是一小部分,都是一种成就感。用我自己的话说是:小人物的幸福感。

很多读者常问我:民工哥,我毕业于一个不怎么好的学校,现在快30了,听讲IT行业35岁就是一个过不去的坎,你觉得我现在学习还来的及吗?还有没有毕业的学生问这种问题。

其实,我想说,对于学习技术,什么时候都不会晚,什么时候都不迟。只需要从现在开始,记得一定要出发,不能只停留在说,要付出行动。一定记得要去改变,去努力做,每天前进0.01,久而久之,量变升华质变。

我由衷感谢那段努力、辛苦付出的自学生涯!

3、一别杭州,深耕运维

2017年4月15号,早上8:00,电话铃声响起。
电话那头:“喂,师傅,我到你们小区门口了,马上进来。”
民工哥:“好的,你进来,左拐再进来,我们在XX栋。”
电话那头:“好的,好的。”

就是这么几句话,结束了我十年的杭漂生涯,我又回到了人生的起点——安徽老家。当时,不少人嘲讽我失败,奋斗十年,依然没有在大城市立足。但我从未后悔,也没放弃走运维这条路。

回到安徽后,我依然坚持运营我的公众号——民工哥技术之路,在上面分享我在运维路上的收获。那时我身边的朋友都来劝我,说我已经吃过亏,不要再继续了;也有人说自动化将取代运维技术员,干运维不是好的出路。

但我认为这是一个错误的理念,并且我坚信运维人员会一直存在,只不过对技能的要求越来越高、越来越深。

运维是一个对知识要求比较全面的一个IT技术岗位,需要掌握的知识很多。拿基础的入门来说:首先需要掌握一定的网络知识,如TCP/IP协议、路由基础、网络的基本命令;其次是Linux的基础知识,如:安装、优化操作系统,一些必备的常用命令,常用的服务部署与配置,常见的系统故障、服务故障的排查与处理。

而对于企业运维人员来说,知识面就更广了:

1、系统基础是必备的前提

2、流行数据库,包括但不限于关系性数据、非关系型数据

4、各种应用服务的性能优化、监控、报警及故障处理

5、各种云平台的操作、容器技术的学习与管理

6、自动化体系的建立与探索

7、系统安全、数据安全

自动化想取代运维,哪有这么容易?!

“冰冻三尺,非一日之寒”,要完全掌握企业运维技能,绝不是一朝一夕就能实现的。作为一个半路出家自学Linux系统的运维人,我知道掌握运维有多不易,因此我决定把企业运维的核心知识系统化、结构化并分享在公众号上,希望能帮助更多想要学习运维的人!

记得是2016年07月08日,我注册并开通了我的公众号并写下了第一篇“文章哥的侃(说)”。当时的公众号名字还不叫“民工哥技术之路”,我只是把自己对于各种技术的理解和实际应用分享在上面,从最开始的网络基础、系统基础到后面的各类应用服务、企业的服务集群架构等,其中像系统基础、MySQL数据库、集群架构这方面的文章都很深爱读者欢迎。尽管上班时间有限,我仍然坚持每天写一篇原创文章,写到如今已经有220+篇原创文章了,公众号也从最开始的无人问津到现在粉丝量9万多。

一路上跌跌撞撞也使我曾有过放弃写文的念头,但看到满屏的“支持,加油”的粉丝留言,又给了我坚持写下去的动力!曾经还有读者私下和我交流,通过我的公众号文章的学习,已经成功在小四线城市顺利入职了,当时听到这个信息,我很高兴也很激动,努力总算还是有收获的,这也实现了通过我的总结帮助更多需要帮助的人的初心。

我以为自己以后就专注在这个公众号上,当一个微不足道的运营写手就好了。直到2018年,人民邮电出版社的编辑找到我,邀请我写一本关于Linux系统运维的书。

我原本是不打算写书的,但在几番劝说下,我发现了写书的好处:一方面是对自己所学知识的查漏补缺,另一方面可以向即将进入或已经入行的Linux系统运维同行们分享一些经验,以便他们在学习的路上少走一些弯路,对企业实际环境的运维工作有一个完整而清晰的认识,从而更快地进入企业运维工程师的角色。

于是,我开启了一段新的旅程——花费两年多的时间,写作了《Linux系统运维指南:从入门到企业实战》这本书。

4、开启下一个十年

其实这本书出版时,我的内心是非常忐忑的:我不是专业的作者,我的书会不会没有人看?又或者读者批评我写的内容?在那几天,我就这样辗转反侧。幸好,公众号里的许多老粉丝都踊跃支持和购买。更幸运的是,《Linux系统运维指南:从入门到企业实战》在京东好评率达到了99%。看着读者收获满满,我感到很欣慰,我两年的写书时间没有白白付出,也实现了我当初写书的初衷和意义。

“已经关注民工哥好长时间了,这本书是民工哥长期以来亲身实践的总结,相信书的内容一定是你想要的。
本书从Linux基础入门,讲到企业常用架构的部署,再到实际运维常用应用服务的部署,有理论,有实践。既适合零基础小白入门,也适合一般的运维人员工作使用。总之,非常推荐这本书!
这次京东的优惠力度也非常大,喜欢的亲们赶紧入手学习了。”

——京东 飞**鱼

“一直很喜欢民工哥的文章,简单易懂,涉及知识面又广,听说出书了,马上下单买了一本来支持,粗略一看,非常详细,是本非常nice的书,不管是小白入门,还是工作的人复习,都非常适合,喜欢的不要犹豫,买!”

——京东 方二狗

很多人说我成就了《Linux系统运维指南:从入门到企业实战》,但我觉得是《Linux系统运维指南:从入门到企业实战》成就了我——坚定了我继续在运维这条路上走下去的决心,也给了我新的启发和方向。

《Linux系统运维指南:从入门到企业实战》

作者: 储成友(民工哥)

内容简介:本书系统全面、由浅入深地介绍了Linux系统运维的知识,以及在企业实际环境中用到的各类服务、架构和运维管理。本书分基础篇、LAMP/LNMP架构篇、应用服务篇和架构运用篇。

在未来,我会不断地学习,自我提升,努力完成自己既定的小目标,还要走更多的路,去更多的地方(旅游),看更多、更美的风景。

也祝愿同在技术路上努力奔跑的你们,能够越来越顺,越来越好!加油!

下一个十年,一切归零,重新出发!

如果我的故事对你有所帮助,有所触动,请点个在赞与转发分享支持一下,感谢大家的阅读、在看与转发支持。

查看原文

赞 14 收藏 0 评论 0

rocky191 关注了用户 · 5月20日

leexiaoran @li1076629390

关注 536

rocky191 关注了专栏 · 5月19日

民工哥技术之路

公众号:民工哥技术之路、《Linux系统运维指南 从入门到企业实战》作者。专注系统架构、高可用、高性能、高并发,数据库、大数据、数据分析、Python技术、集群中间件、后端等开源技术分享。

关注 16084

认证与成就

  • 获得 57 次点赞
  • 获得 25 枚徽章 获得 2 枚金徽章, 获得 6 枚银徽章, 获得 17 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-11-07
个人主页被 1.1k 人浏览