
context 是看完 React 提供的特性,可以实现任意层级组件之间的码知数据传递。
可能大家用过 context,道绕但是过P改不知道它是怎么实现的。
本文就从源码层面来讲下 cotnext 的看完原理,而且我们能从中发现一些 hack 的码知小技巧。
首先,道绕我们先过一下 context 的过P改使用方式:
有这样的 3个组件,One、看完Two、码知Three:

我们想不通过 props 从 One 传递数据到 Three,道绕这时候就可以用 context 了:

调用 createContext 来创建 context 对象,过P改初始化数据是看完 dong。
Three 组件里就可以通过 useContext 把 context 数据取出来了:


除了初始化的码知时候可以传入,后面也可以通过 Provider 来修改 context 数据:

我们通过 Provider 传入了新的道绕 value,覆盖了初始值,这样 useContext 拿到的值就变了:

函数组件是用 useContext 的 hook 来取,b2b供应网class 组件则是用 Consumer 来取:


我们依然是通过 createContext 创建 context 对象,通过 Provider 修改了 value。
现在 Three 变成了 class 组件,所以使用 context 的方式要通过 Consumer,它的 children 部分传入 render 函数,参数里就能拿到 context 数据:


这分别是 class 组件和 function 组件使用 context 的方式,我们小结一下:
context 使用 React.createContext 创建,可以传入初始值,后面也可以通过 Provider 来修改其中的值,使用 context 值的时候,如果是 function 组件,可以通过 useContext 的 hook 来取,而 class 组件是用 Consumer 传入一个 render 函数的方式来取。
学会了 context 怎么用,我们再来看下它的实现原理:
首先我们看下 createContext 的源码:

它创建了一个 context 对象,有 _currentValue 属性,一看就是保存值的服务器托管,还有 Consumer 和 Provider 两个属性。
Consumer 和 Provider 都是通过 _context 保存了 context 对象的引用。
并且它们都有 $$typeof 来标识类型。
这就是 context 对象的结构:


那这个 context 对象是怎么结合到 React 渲染流程里的呢?
就是通过 jsx 结合的呀:


jsx 编译以后会产生 render function。
比如上面那段 jsx 编译后是这样的:


新版 React 不调用 React.createElement 了,而是 jsx 函数,也就是上面的 jsxDev。

看这第一个参数是啥,有 $$typeof 和 _context 属性,不就是我们传入的 context.Provider 么:


value 是在 props 参数里传入的:


jsx 执行会产生一个个 vdom:


这样 context 就保存到了 vdom 节点上。


那递归渲染的时候不就能从 vdom 拿到 context 了么?
别着急,React 现在不是 vdom 直接渲染了,亿华云计算而是会先把 vdom 转成 fiber,这个过程叫做 reconcile:


fiber 的结构中保存了兄弟节点和父节点的引用,这是 vdom 所没有的,vdom 只有子节点的引用,所以 vdom 变成 fiber 以后就变成了可打断的,因为就算断了也能找到兄弟节点和父节点继续处理。
那保存在 vdom 中的 context 不就自然的转移到了 fiber 节点上了么?
创建 fiber 的代码是这样的:


调用 createFiber 最终会 new FiberNode

然后也是创建一个对象:

这个和 vdom 差别不大,只不过属性不一样了。
你看这里的 fiber.type,不就是保存在 vdom 上的 context.Provider 对象么?


那之后这个 fiber 节点是怎么处理的呢?
你会发现在处理 fiber 节点的时候,会判断 fiber.tag,不同的类型做不同的处理:


FunctionComponent 和 ClassComponent 的 fiber 节点的处理就不同。
下面可以找到 ContextProvider 的:


它的实现就是修改了 context 中的值:

pushProvider 就是最终修改 context 值的地方:

通过 _context 拿到了 Provider 所引用的 context 对象,然后修改它的 _currentValue 属性,也就是 context 中的值。
对照着这个 context 对象的结构一看就明白了:


同理,后面处理到 Consumer 的时候也是这样拿到 context 的:


ContextConsumer 的 fiber 节点也会做专门的处理:

workInProgress 就是当前 fiber 节点,它的 type 保存了 context.Consumer 对象。
我们通过 readContext 拿到其中的值,也就是取 _currentValue 属性。
拿到最新的 context 中的值后就触发子组件的渲染:

所以说为什么 Consumer 必须要传入一个 render 函数作为子节点不就清楚了么:


这样我们就取到了 context 中的值,并触发了子组件的渲染。
使用 context 的方式还有 useContext 的 hook,其实那个也是一样的:


useContext 也是调用了 readContext 来读取了 context 的 _currentValue 属性:


当然,useContext 的 context 不是从 fiber.type 来取的,而是用的传入的 context:


但是都是引用的同一个对象,在 Provider 里修改了 context 的 value,这里取到的 context 同样也是新的。
我们小结下 context 的实现原理:
createContext 会创建 context 对象,它有 _currentValue 保存值,还引用了 Provider 和 Consumer 两个对象,Provider 和 Consumer 里有 _context 属性引用了 context。
在 jsx 渲染的时候会把 Provider 和 Consumer 对象保存到 vdom 上,后面 reconcile 的时候会转移到 fiber 的 type 属性上,处理 fiber 节点的时候会根据类型做不同的处理:
如果是 Provider,就会根据传入的 value 修改 context 的值,也就是 _currentValue 属性
如果是 Consumer,则会读取 context 的值然后触发子组件的渲染。
函数组件会使用 useContext 的 hook,最终也是读取了同一个 context 的 _currentValue 的 值
理清了 context 的实现原理,我们是不是能发现一些 hack 的技巧呢?
比如 Provider 其实就是修改 _currentValue 的,那我们自己修改 context._currentValue 不就不用 Provider 了?
试一下:
用 Provider 是这样的:

其实直接修改 _currentValue 也可以:

但是不推荐这样写,因为这是个私有属性,万一哪一天变了呢?
context 是 React 提供的任意层级组件之间通信的机制,我们先过了一遍它的使用方式:
通过 createContext 来创建 context 对象,可以传入初始值,可以通过 jsx 里的 context.Provider 来修改 context 值,通过 context.Consumer 来拿到 context 的值,如果是函数组件,是通过 useContext 的 hook 来取。
然后通过源码理清了 context 的实现原理:
jsx 里的 Provider 和 Consumer 对象会被保存到 vdom 中,最后会转移到 fiber 节点的 type 属性上,fiber 处理的时候,对 Provider 会修改 context 的 value,而 Consumer 则会取出 context 的值然后触发子组件渲染。函数组件的 useContext 的 hook 也是从同一个 context 对象读取的数据。
然后我们发现了绕过 Provider 修改 context 的方式,就是直接修改 _currentValue,但是不推荐这样做,因为私有属性不一定啥时候就变了。
context 是这样实现的,其他特性的实现原理也大同小异,只不过是挂到了不同的属性上,处理 fiber 的时候做了分成了不同的类型来处理。
理清 context 的原理之后,你是否对 vdom、fiber,还有 reconcile 的过程都有更深的理解了呢?
ubuntu怎么开启root帐号?ubuntu 的root账户具有最高的系统权限,它类似于windows系统中的管理员账号,但是比windows系统中管理员账号的权限更高,一般都情况下不要使用root账户,但是有的时候还是要使用root账户,下面就为大家介绍ubuntu 开启root帐号方法!说明:小编的这个建议只适合于10.10之前版本的ubuntu系统,后面的11.04,11.10。。。14.04系统因为采用的默认桌面不同,所以本经验不适用,若要使用,那么请安装GNOME桌面ubuntu 开启root帐号方法:1、点击系统菜单栏中的“应用程序”,然后点击附件,之后在打开的附件子菜单中选择“终端”2、点击终端后就打开如下图所示的终端工具,大家要做的所有操作都在这个窗口中进行3、其实root账户是存在的,只是需要我们给它设置一个密码,然后使用的时候用root用户名登陆,然后输入对应的密码就就以root用户登录了,所以开启root账户,实际上就是给root用户设置一个密码的过程,下面我们就来给root设置密码,另外还需要注意的是,只能使安全ubuntu系统的时候创建的用户账号才能启用root账号,使用下面的命令来给root账号设置密码:sudo passwd root执行上面的命令后,就会提示要求输入当前用户的密码4、确认我们的密码正确后,就会提示“Enter new UNIX password”,这个就是设置密码的提示,输入大家要设置的密码,注意在设置密码的时候是看不到任何字符的,只有我们自己注意了5、输入密码后敲回车键,之后会提示我们再次确认密码,输入确认密码,然后按Enter回车键6、密码设置成功,这样root用户也就开启了,以后大家要使用root账号的时候只要使用相关切换命令就可以了,具体怎么切换到root账号的命令请看小编另外一个经验的分享END以上就是ubuntu怎么开启root帐号的方法,希望对大家有所帮助!
一次非常有意思的 SQL 优化经历:从 30248.271s 到 0.001s