React源码浅析

1 Redux--js应用的状态容器

在这里插入图片描述

a. 用户通过dispatch一个action,来提交对数据的修改。
store.dispatch(action)
b. store自动调用reducers,并且传入两个参数:旧stateactionreducers会返回新的state
newState = reducers(state,action)
c. state一旦有变化,store就会调用监听函数
store.subscribe(listener)
d. 触发重新渲染view

1.1 创建store

在src文件夹下新建store.js

// ./src/store.js
import { createStore, applyMiddleware } from './redux'
function Reducer1(state = 0, { type, payload=1 }) {
	switch(type){
		case 'ADD':
			return state + payload
		case 'MUNIS':
			return state - payload
		default:
			return state
	}
}
function Reducer2 (state = {num:0}, {type, payload=1}){
	switch(type){
		case 'ADD2':
			return {...state, num:state.num + payload}
		default:
			return state
	}
}
// 创建store,combineReducers函数组合多个reducer。
// applyMiddleware 中间件函数,执行副作用,比如异步请求数据,延迟执行等
// thunk是加强函数,为中间件服务
const store = createStore(
	combineReducers({count:Reducer1, count2:Reducer2}),
	applyMiddleware(thunk)
)
export default store
// next 为上次返回的函数,action为接收的参数
// 如果action为函数,则执行的同时,传递俩参数,如果不是,则直接执行next
function thunk({dispatch, getState}) {
	return next => action => {
		if(typeof action === "function"){
			return action(dispatch,getState)
		}
		next(action)
	}
}
// 聚合多个reducers,hasChange通过新旧state比对,及新旧state个数的比对来判断是否更新state
function combineReducers(reducers) {
	return function combination(state = {}, action) {
		let newState = {}
		let hasChange = false
		for(let key in reducers) {
			const reducer = reducers[key]
			newState[key] = reducer(state[key],action)
			hasChange = hasChange || newState[key] !== state[key]
		}
		hasChange = hasChange || Object.keys(newState).length !== Object.keys(state).length
		return hasChange ? newState : state
	}
}

在src文件夹下新建index.js文件

  • subscribe 监听state变化
  • dispatch 提交更新
  • getState 获取状态值
  • forceUpdate 刷新状态
// ./src/index.js
import React,{PureComponent} from 'react'
import {render} from 'react-dom'
import store from './store.js'

class App extends PureComponent{
	componentDidMount(){
		store.subscribe(() => {
			this.forceUpdate()
		})
	}
	count = () => {
		store.dispatch({
			type:"ADD",
			payload:100
		})
	}
	asynccount = () => {
		store.dispath(() => {
			setTimeout(() => {
				store.dispatch({
					type:"ADD",
					payload:100
				})
			},1000)
		})
	}
	render(){
		return <div>
			<p>{store.getState().count}</p>
			<button onClick={this.count}>ADD</button>
			<button onClick={this.asynccount}>asyncADD</button>
		</div>
	}
}

render(<App />,document.getElementById('root'))

在src文件夹下新建redux.js文件

// ./src/redux.js
import createState from './createState'
import applyMiddleware from './applyMiddleware'
export { createState, applyMiddleware }

在src文件夹下新建createState.js文件

// ./src/createState.js
export default function createState(reducer, enhance){
	if(enhance) {
		// 如果有加强函数,比如applyMiddleware,则执行,并且传入函数createState,reducer
		return enhance(createState)(reducer)
	}
	let currentState // 当前状态值
	let scribeList = [] // 监听列表
	const getState = () => {
		return currentState
	}
	const subscribe = (back) => {
		scribeList.push(back)
		// 返回取消监听函数
		return () => {
			const index = scribeList.indexof(back)
			scribeList.splice(index, 1)
		}
	}
	const dispatch = ({type, payload}){
		currentState = reducer(currentState, {type, payload})
		scribeList.map(lister => lister())
	}
	// 初始化state值
	dispatch({type:"REDUXXXXXXXX/0000000"})
	return { getState, subscribe, dispatch}
}

在src文件夹下新建文件applyMiddleware.js

// ./src/applyMiddleware.js
export default function applyMiddleware(...applyMiddles){
	return createState => reducer => {
		// 获取store
		const store = createState(reducer)
		// 获取需要传递的参数
		const midApi = {
			getState: store.getState,
			dispatch: action => store.dispatch(action)
		}
		// 执行传递给中间件的函数,并生成函数数组
		const applyMiddlewareChain = applyMiddles.map(applyMiddle => 
			applyMiddle(midApi)
		)
		// 组合执行函数数组,并且把dispatch传递进去
		const dispatch = compose(...applyMiddlewareChain)(store.dispatch)
		// 返回加强版dispatch
		return {...store,dispatch}
	}
}
function compose(...funs){
	if(funs.length === 0)return args => args
	if(funs.length === 1){
		return funs[0]
	}
	// 使用reduce函数,组合执行传递进来的函数,并传递参数,最终返回一个函数
	return funs.reduce((a,b) => args => a(b(args)))
}

2 React-Redux

  • Provider 为后代组件提供store
  • connect为组件提供数据和更新方法

修改src文件夹下的index.js文件

import React,{PureComponent} from 'react'
import {render} from 'react-dom'
import store from './store.js'
import {Provider, connect, bindActionCreators } from './ReactRedux.js'

// @为装饰器
// connect用于React与store的连接,返回一个新的已经与store连接的组件类
@connect(
	// mapStateToProps函数,返回数据
	state => ({count:state.count, count2:state.count2}),
	// mapDispatchToProps函数,返回更新方法
	// bindActionCreators函数,一个对象的多个方法传入dispatch,并执行
	(dispatch) => {
		let creators = {
			Add: () => ({type:"ADD"}),
			Add2: () => ({type:"ADD2"})
		}
		creators = bindActionCreators(creators, dispatch)
		// 返回dispatch方法,使之在class里可以使用;
		// 返回所有的更新方法
		return {dispatch, ...creators}
	}
)
class App extends PureComponent{
	count = () => {
		this.props.dispatch({
			type:"ADD",
			payload:100
		})
	}
	asynccount = () => {
		this.props.dispath(() => {
			setTimeout(() => {
				this.props.dispatch({
					type:"ADD",
					payload:100
				})
			},1000)
		})
	}
	render(){
		const { count, count2, Add, Add2} = this.props
		return <div>
			<p>{count}</p>
			<button onClick={this.count}>点击</button>
			<button onClick={Add}>ADD</button>
			<button onClick={Add2}>{count2.num}</button>
			<button onClick={this.asynccount}>asyncADD</button>
		</div>
	}
}

render(<Provider store={store}>
	<App />
</Provider>,
document.getElementById('root'))

在文件夹src下新建ReactRedux.js文件

// ./src/ReactRedux.js
import React from 'react'

// 创建跨组件通信Context
const Context = React.createContext()
export function Provider({store, children}){
	return <Context.Provider value={store}>{children}</Context.Provider>
}
export const connect = (
	mapStateToProps=state => state,
	mapDispatchToProps
) => WrapperComponent => props => {
	const store = React.useContext(Context)
	const { getState, dispatch} = store
	let dispatchProps = {dispatch}
	const stateProps = mapStateToProps(getState())
	if(typeof mapDispatchToProps === "object"){
		dispatchProps = bindActionCreators(mapDispatchToProps,dispatch)
	}else if(typeof mapDispatchToProps === "function"){
		dispatchProps = mapDispatchToProps(dispatch)
	}
	// 创建一个自更新函数foreUpdate
	const [ignored,foreUpdate] = React.useReducer(x=>x+1,0)
	// 同步执行副作用,及监听函数
	React.useLayoutEffect(() => {
		const unsubscribe = store.subscribe(() => {
			foreUpdate()
		})
		// 组件卸载时,清除监听函数
		return () => {
			if(unsubscribe)unsubscribe()
		}
	},[store])
	return <WrapperComponent {...props} {...dispatchProps} {...stateProps} />
}
export function bindActionCreators(creators,dispatch){
	const obj = {}
	for(let key in creators){
		obj[key] = actionCreator(creators[key],dispatch)
	}
	return obj
}
function actionCreator(creator,dispatch){
	return (...args) => dispatch(creator(...args))
}

3 虚拟DOM

3.1 为什么需要虚拟DOM呢,先来看看webkit引擎的处理流程,如下图:

在这里插入图片描述

3.2 处理流程分为5步:

1,用HTML分析器,分析html元素,创建一颗DOM树;
2,用css分析器,分析css文件和inline样式,创建一张样式表;
3,将DOM树和样式表关联起来,生成Render树;
4,浏览器开始布局,为每个Render树上的节点,确定一个在显示屏上出现的精确坐标值。
5,调用每个节点的paint方法,将节点显示出来。

当你操作真实的DOM时,即使一个微小的操作,代价也是高昂的,举个栗子:
打印一个简单的div元素,查看它的属性

var div = document.createElement("div");
var str = "";
var allNum = 0
for(var key in div){
	str = str + key + " ";
	allNum++
}
console.log(str,allNum)

打印结果如下图
在这里插入图片描述

3.3 什么是虚拟DOM

虚拟DOM是用js对象表示DOM的信息和结构,是UI的虚拟化表现显示,被保存于内存中,并通过如ReactDOM等类库使之与真实的DOM同步。当需要修改UI的时候,重新渲染这个js对象。

3.4 为什么使用虚拟DOM

真实DOM操作很昂贵,消耗大。轻微的操作都可能导致页面重新排版。相对于虚拟DOM处理js对象,js处理起来更快,更简单。通过diff算法对比新旧js对象,可以批量的,最小化的执行dom操作,从而提高性能。

3.5 生成虚拟DOM

Babel-loader通过React.createElement(…)转换jsx代码,生成vdom,即虚拟domJSX代码如下:

// ./src/index.js
import React from './myreact/'
import ReactDOM from './myreact/react-dom'
const jsx = <div className="border">
	<p>HELLO</p>
	<a href="#">WORLD</a>
</div>
ReactDOM.render(jsx,document.getElementById("root"))

新建文件夹myreact,并在文件夹里新建index.js和react-dom.js文件

// ./src/myreact/index.js
// 创建render函数

function render(vnode,container){
	console.log('vnode-----',vnode)
}
export default { render }
// ./src/myreact/react-dom
// 创建createElement函数

function createElement(type, config, ...children){
	const props = {
		...config,
		children: children.map(child => typeof child === 'Object' ? child : 
		createTextNode(child))
	}
	return {
		type,
		props
	}
}
//处理成对象,方便统一处理
function createTextNode(text){
	return {
		type:"TEXT",
		props:{
			children: [],
			nodeValue: text
		}
	}
}
export default { createElement }

通过上面的代码打印出来的虚拟DOM树如图:
在这里插入图片描述

4 React 三大API(render,createElement,component

4.1 render函数将虚拟DOM转换成真实DOM,并且插入到页面中

// ./src/myreact/react-dom.js
// 创建render函数

function render(vnode,container){
	console.log('vnode-----',vnode)
	const node = createNode(vnode) // 把虚拟DOM转化成真实DOM
	container.appendChild(node) // 把node插入到container中
}
// 生成真实的dom节点
function createNode(vnode){
	let node = null
	const { type, props } = vnode
	if(type === 'TEXT'){
		// 如果是文本,则创建文本节点
		node = document.createTextNode("")
	}else if(typeof type === 'string'){
		// 证明就是html节点,比如div,span,p等标签,直接创建标签节点
		node = document.createElement(type)
	}
	// 循环遍历子元素
	reconcileChildren(props.children, node)
	// 添加属性
	updateNode(node,props)
	return node
}

function reconcileChildren(children, node){
	for(let i=0,len=children.length; i<len; i++ ){
		let child = children[i]
		// child是vnode,那需要把vnode转化成真实dom节点,然后插入父节点node中
		render(child,node)
	}
}

function updateNode(node, nodeValue){
	//过滤掉children
	Object.keys(nodeValue).filter(k => k!=='children')
	.forEach(k => {
		//把props的属性添加到节点上
		node[k] = nodeValue[k]
	})
}
export default { render }

到此,节点及文本就显示在页面里了。在src/index.js里添加类组件

// ./src/index.js
import React from './myreact/'
import ReactDOM from './myreact/react-dom'
import Component from './myreact/Component'

//class类组件,使用到Component API
class ClassComponent extends Component {
	render(){
		return <div className="class border">{this.props.name}</div>
	}
}
function FunctionComponent({name}){
	return <div className="function border">{name}</div>
}
const jsx = <div className="border">
	<p>HELLO</p>
	<a href="#">WORLD</a>
	<ClassComponent name="class component" />
	<FunctionComponent name="function component" />
</div>
ReactDOM.render(jsx,document.getElementById("root"))

在myreact文件夹里新建文件Component.js

// ./src/myreact/Component.js

class Component{
	static isClassComponent={}
	coustructor(props){
		this.props = props
	}
}
export default Component

在文件./src/myreact/react-dom.js的createNode函数中添加代码

// ./src/myreact/react-dom.js

// 生成真实的dom节点
function createNode(vnode){
	let node = null
	const { type, props } = vnode
	if(type === 'TEXT'){
		// 如果是文本,则创建文本节点
		node = document.createTextNode("")
	}else if(typeof type === 'string'){
		// 证明就是html节点,比如div,span,p等标签,直接创建标签节点
		node = document.createElement(type)
	}else if(typeof type === 'function'){
		//通过Component类里定义的静态参数,判断是类还是函数
		node = type.isClassComponent ? 
		updateClassComponent(vnode) : updateFunctionComponent(vnode)
	}
	// 循环遍历子元素
	reconcileChildren(props.children, node)
	// 添加属性
	updateNode(node,props)
	return node
}
// 处理类组件
function updateClassComponent(vnode){
	const {type, props} = vnode
	//类需要new来运行
	const cmp = new type(props)
	//类的节点在render函数里
	const vvnode = cmp.render()
	//返回真实的dom节点
	const node = createNode(vvnode)
	return node
}
// 处理函数组件
function updateFunctionComponent(vnode){
	const {type, props} = vnode
	const vvnode = type(props)
	const node = createNode(vvnode)
	return node
}

5 diff算法

React通过执行Render函数就会生成一颗树,state或者props变化后,Render函数又会生成另一颗树。React需要基于两颗树之间的差异来判断如何有效率的更新UI,以保证UI和最新的树保持一致。
有一些通用的算法,可以实现从一颗树到另一颗树的最小操作数。然而即使最前沿的算法,该算法的复杂度也是O(n³),n为元素个数。如果展示1000个元素,计算量在十亿级别的,消耗过于高昂。

于是React在一下两个假设下,提出一个O(n)的启发式算法:
1,两个不同类型的元素,会产生不同的树。
2,开发者通过keyprop来暗示哪些子元素在不同的渲染下能保持稳定。

diffing算法,应运而生,算法复杂度为O(n)

diff策略

1,同级比较,Web UI中的DOM跨层级的移动操作非常少,可以忽略不计。
在这里插入图片描述

2,拥有不同类型的两个组件,将会生成不同的树型结构。
3,开发者可以通过key,prop来暗示哪些子元素在不同的渲染下能保持稳定。

diff过程

对比两个虚拟dom时,会有三种操作删除,替换,更新
vnode是现在的虚拟dom,newVnode是新的虚拟dom
1,删除:newVnode不存在时
在这里插入图片描述
2,替换:vnodenewVnode类型不同或key不同
在这里插入图片描述
3,更新:有相同的类型和key,但是vnodenewVnode不同时
在这里插入图片描述

6,Fiber

6.1,为什么需要fiber

1,对于大型项目来说,组件树会很大,这个时候遍历递归的成本就会很大,会造成主线程被持续占用,结果就是主线程上的布局,动画等周期性任务就无法立即得到处理,造成视觉上的卡顿,影响用户体验。
2,任务分解的意义,解决上面的问题。
3,增量渲染(把渲染任务拆分成块,均到多帧)
4,更新时能够暂停,终止,复用渲染任务。
5,给不同类型的更新赋予优先级
6,并发方面新的基础能力
7,更流畅

6.2,实现fiber

window.requestIdleCallback()方法能够在浏览器空闲时间段调用函数排队。

6.3,fiber的数据结构-单向链表

在这里插入图片描述
fiber为起点,第一个子biberchildchild的兄弟元素为sibling,每个元素都有return,指向父元素。

6.4,fiber的数据结构解析:

  • type:标记当前节点的类型
  • key:标记唯一性
  • props:属性
  • child:第一个子元素
  • sibling:下一个兄弟元素
  • return:指向父元素
  • node:真实的dom节点
  • base:记录下当前的节点
  • effectTag:“PLACEMENT”,“UPDATE”,“DELETION” 对节点的操作类型
// ./kreact/react-dom

// 下一个要执行的fiber, 数据结构就是fiber
let nextUnitOfWork = null;
// work in propgress 正在进行当中的,结构类型是fiber
let wipRoot = null;
function render(vnode, container) {
	wipRoot = {
		node: container,
		props:{
			children: [ vnode ]
		},
		base: null
	}
	nextUnitOfWork = wipRoot
  // const node = createNode(vnode)
  // console.log('vdom----', vnode)
  // container.appendChild(node)
}
// 创建节点
function createNode(vnode) {
  let node = null
  const { type, props } = vnode
  if (type === 'TEXT') {
    node = document.createTextNode('')
  } else if (typeof type === 'string') {
    node = document.createElement(type)
  } 
  //else if (typeof type === 'function') {
   // node = type.isClassComponent
   //   ? updateClassComponent(vnode)
   //   : updateFunctionComponent(vnode)
 // }
  // reconcileChildren(props.children, node)
  updateNode(node, props)
  return node
}
// 添加属性到真实dom里
function updateNode(node, nodeValue) {
  Object.keys(nodeValue)
    .filter((k) => k !== 'children')
    .forEach((k) => {
      node[k] = nodeValue[k]
    })
}
// 遍历一下子节点-old
//function reconcileChildren(children, node) {
//  for (let i = 0, len = children.length; i < len; i++) {
//    render(children[i], node)
//  }
//}
// 协调一下子节点
// 1,给workInProgress添加一个child节点,就是children的第一个子节点形成的fiber
// 2,形成fiber架构,把children里节点遍历下,形成fiber链表状
function reconcileChildren(workInProgress, children) {
	let prevSibling = null // 保存每次循环生成的fiber
  for (let i = 0, len = children.length; i < len; i++) {
  	let child = children[i];
  	// 组装fiber结构
  	let newFiber = {
		type:child.type,
		props:child.props,
		node:null,
		base:null,
		return:workInProgress,
		effectTag:"PLACEMENT" //PLACEMENT,UPDATE,DELETION
	}
	if(i === 0){
		workInProgress.child = newFiber
	}else{
		prevSibling.sibling = newFiber // 上一次fiber的sibling指向新生成的fiber
	}
	prevSibling = newFiber //储存当前生成的fiber
  }
}
// 协调class组件
function updateClassComponent(fiber) {
  	const {type,props} = fiber
  	const cmp = new type(props)
  	const children = [cmp.render()]
  	reconcileChildren(fiber,children)
}
// 协调函数组件
function updateFunctionComponent(fiber) {
  	const {type,props} = fiber
  	const children = [type(props)]
  	reconcileChildren(fiber,children)
}
// 协调原生元素
function updateHostComponent(fiber){
	if(!fiber.node){
		fiber.node = createNode(fiber)
	}
	const {children} = fiber.props
	reconcileChildren(fiber,children)
}
function performUnitOfWork(fiber){
	// step1:执行更新当前fiber,
	const {type} = fiber
	if(typeof type === "function"){
		type.isClassComponent ? 
		updateClassComponent(fiber) : 
		updateFunctionComponent(fiber)
	}else{
		// 原生标签的更新函数
		updateHostComponent(fiber)
	}
	// step2:并且返回下一个要执行的fiber
	// 原则就是:先看下有没有子元素
	if(fiber.child){
		return fiber.child
	}
	// 没有子元素,找兄弟元素
	let nextFiber = fiber
	while(nextFiber){
		if(nextFiber.sibling){
			return nextFiber.sibling
		}
		nextFiber = nextFiber.return
	}
}
// 浏览器空闲时段执行的回调函数
function workloop(deadline){
	while(nextUnitOfWork && deadline.timeRemaining()>1){
		// 执行更新当前fiber,并且返回下一个要执行的fiber
		nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
		if(!nextUnitOfWork && wipRoot){
			//没有下一个任务了,执行提交
			commitRoot()
		}
	}
	requestIdleCallback(workloop)
}
// 浏览器空闲时段执行
requestIdleCallback(workloop)
// 提交函数
function commitRoot(){
	commitWorker(wipRoot.child)
	wipRoot = null //当前的节点置为空,避免死循环
}
function commitWorker(fiber){
	if(!fiber)return
	// parentNode是fiber的离的最近的父或祖先节点
	let parentNodeFiber = fiber.return
	while(!parentNodeFiber.node){
		parentNodeFiber = parentNodeFiber.return
	}
	let parentNode = parentNodeFiber.node
	// fiber 有node节点
	if(fiber.effectTag === "PLACEMENT" && fiber.node !== null){
		parentNode.appendChild(fiber.node)
	}
	// 添加子元素
	commitWorker(fiber.child)
	// 添加兄弟元素
	commitWorker(fiber.sibling)
}
export default { render }

参考资料:
1,京东内部项目沉淀。
2,开课吧-前端全栈工程师,培训内容之React篇。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值