前言
该会的还是必须得会,千万不能怕麻烦。一步步拆开总能到终点。
实现虚拟dom
jsx语法都是通过babel转译完成。
首先是个普通的类组件:
class App extends Component {
render ( ) {
return (
< div>
< p> 1 < / p>
< button> + < / button>
< / div>
)
}
}
class App extends Component {
render ( ) {
return React. createElement (
"div" ,
null ,
React. createElement ( "p" , null , "1" ) ,
React. createElement ( "button" , null , "+" )
) ;
}
}
分析下这里需要做2个东西,一个是Component,一个是React里做个createElement方法。
看一下react源码里react是什么玩意?是类还是啥?答案是个对象,源码里这么写:
const React = {
Children: {
map,
forEach,
count,
toArray,
only,
} ,
createRef,
Component,
PureComponent,
createContext,
forwardRef,
lazy,
memo,
Fragment: REACT_FRAGMENT_TYPE ,
StrictMode: REACT_STRICT_MODE_TYPE ,
Suspense: REACT_SUSPENSE_TYPE ,
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
version: ReactVersion,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
} ;
看一下createElement和component是个啥?
createElement源码:
export function createElement ( type, config, children) {
let propName;
const props = { } ;
let key = null ;
let ref = null ;
let self = null ;
let source = null ;
if ( config != null ) {
if ( hasValidRef ( config) ) {
ref = config. ref;
}
if ( hasValidKey ( config) ) {
key = '' + config. key;
}
self = config. __self === undefined ? null : config. __self;
source = config. __source === undefined ? null : config. __source;
for ( propName in config) {
if (
hasOwnProperty. call ( config, propName) &&
! RESERVED_PROPS . hasOwnProperty ( propName)
) {
props[ propName] = config[ propName] ;
}
}
}
const childrenLength = arguments. length - 2 ;
if ( childrenLength === 1 ) {
props. children = children;
} else if ( childrenLength > 1 ) {
const childArray = Array ( childrenLength) ;
for ( let i = 0 ; i < childrenLength; i++ ) {
childArray[ i] = arguments[ i + 2 ] ;
}
props. children = childArray;
}
if ( type && type. defaultProps) {
const defaultProps = type. defaultProps;
for ( propName in defaultProps) {
if ( props[ propName] === undefined) {
props[ propName] = defaultProps[ propName] ;
}
}
}
return ReactElement (
type,
key,
ref,
self,
source,
ReactCurrentOwner. current,
props,
) ;
}
function Component ( props, context, updater) {
this . props = props;
this . context = context;
this . refs = emptyObject;
this . updater = updater || ReactNoopUpdateQueue;
}
Component. prototype. isReactComponent = { } ;
Component. prototype. setState = function ( partialState, callback) {
invariant (
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null ,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.' ,
) ;
this . updater. enqueueSetState ( this , partialState, callback, 'setState' ) ;
} ;
Component. prototype. forceUpdate = function ( callback) {
this . updater. enqueueForceUpdate ( this , callback, 'forceUpdate' ) ;
} ;
function ComponentDummy ( ) { }
ComponentDummy. prototype = Component. prototype;
function PureComponent ( props, context, updater) {
this . props = props;
this . context = context;
this . refs = emptyObject;
this . updater = updater || ReactNoopUpdateQueue;
}
const pureComponentPrototype = ( PureComponent. prototype = new ComponentDummy ( ) ) ;
pureComponentPrototype. constructor = PureComponent;
Object. assign ( pureComponentPrototype, Component. prototype) ;
pureComponentPrototype. isPureReactComponent = true ;
Component 那个有点多,我画个图来梳理下Component到底是个啥
其中,红色底色表示函数,绿色表示对象,浅灰色表示其公有属性,白色表示原型对象上的属性。
为什么要设计成这样?其实就是个继承,但是为什么操作的这么骚?先不用管。先了解结构就行。
createElement可以发现它是返回的一个ReactElement,
这个ReactElement源码:
const ReactElement = function ( type, key, ref, self, source, owner, props) {
const element = {
$$typeof : REACT_ELEMENT_TYPE ,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
} ;
return element;
} ;
所以createElement方法最终返回的是ReactElement创建出来的对象。
createElement主要是把传来的config里面几个关键的属性提取出来,分配孩子和属性,最后让ReactElement造出个对象。
经分析,其中source和self生产模式无用。先可以精简成这样:
const RESERVED_PROPS = {
key: true ,
ref: true ,
__self: true ,
__source: true ,
} ;
function hasValidRef ( config) {
return config. ref !== undefined;
}
function hasValidKey ( config) {
return config. key !== undefined;
}
const ReactCurrentOwner= {
current: null
}
const hasSymbol = typeof Symbol === 'function' && Symbol. for ;
const REACT_ELEMENT_TYPE = hasSymbol
? Symbol. for ( 'react.element' )
: 0xeac7 ;
const ReactElement = function ( type, key, ref, owner, props) {
const element = {
$$typeof : REACT_ELEMENT_TYPE ,
type: type,
key: key,
ref: ref,
_owner: owner,
props: props,
} ;
return element;
} ;
export function createElement ( type, config, children) {
let propName;
const props = { } ;
let key = null ;
let ref = null ;
if ( config != null ) {
if ( hasValidRef ( config) ) {
ref = config. ref;
}
if ( hasValidKey ( config) ) {
key = '' + config. key;
}
for ( propName in config) {
if (
hasOwnProperty. call ( config, propName) &&
! RESERVED_PROPS . hasOwnProperty ( propName)
) {
props[ propName] = config[ propName] ;
}
}
}
const childrenLength = arguments. length - 2 ;
if ( childrenLength === 1 ) {
props. children = children;
} else if ( childrenLength > 1 ) {
const childArray = Array ( childrenLength) ;
for ( let i = 0 ; i < childrenLength; i++ ) {
childArray[ i] = arguments[ i + 2 ] ;
}
props. children = childArray;
}
if ( type && type. defaultProps) {
const defaultProps = type. defaultProps;
for ( propName in defaultProps) {
if ( props[ propName] === undefined) {
props[ propName] = defaultProps[ propName] ;
}
}
}
return ReactElement (
type,
key,
ref,
ReactCurrentOwner. current,
props,
) ;
}
const emptyObject = { } ;
export function Component ( props, context, updater) {
this . props = props;
this . context = context;
this . refs = emptyObject;
this . updater = updater
}
Component. prototype. isReactComponent = { } ;
Component. prototype. setState = function ( partialState, callback) {
this . updater. enqueueSetState ( this , partialState, callback, 'setState' ) ;
} ;
Component. prototype. forceUpdate = function ( callback) {
this . updater. enqueueForceUpdate ( this , callback, 'forceUpdate' ) ;
} ;
function ComponentDummy ( ) { }
ComponentDummy. prototype = Component. prototype;
function PureComponent ( props, context, updater) {
this . props = props;
this . context = context;
this . refs = emptyObject;
this . updater = updater
}
const pureComponentPrototype = ( PureComponent. prototype = new ComponentDummy ( ) ) ;
pureComponentPrototype. constructor = PureComponent;
Object. assign ( pureComponentPrototype, Component. prototype) ;
pureComponentPrototype. isPureReactComponent = true ;
这时候使用react改成自己的react ,就已经可以工作了!
import React, { Component} from './react' ;
import ReactDOM from 'react-dom' ;
class App extends Component {
render ( ) {
return React. createElement (
"div" ,
null ,
React. createElement ( "p" , null , "1" ) ,
React. createElement ( "button" , null , "+" )
) ;
}
}
ReactDOM. render (
< App> < / App> ,
document. getElementById ( 'root' )
) ;
使用Jsx一样可以工作,因为是靠babel转译的语法。
下面实现下React.Children.map这个方法。看一下官方怎么用的:
class Child extends Component {
render ( ) {
console. log ( this . props. children) ;
const mappedChildren = React. Children. map (
this . props. children,
( item, index) => (
[ < div key= { `div ${ index} A` } > { item} < / div> , < div key= { `div ${ index} B` } > { item} < / div> ]
)
) ;
console. log ( mappedChildren) ;
return (
< div>
{ mappedChildren}
< / div>
)
}
}
class App extends Component {
render ( ) {
return (
< Child>
< div> child1< / div>
< div key= "key2" > child2< / div>
< div key= "key3" > child3< / div>
{
[
< div key= "key4" > child4< / div> ,
< div key= "key5" > child5< / div> ,
< div key= "key6" > child6< / div>
]
}
< / Child>
)
}
}
0: {$$typeof: Symbol(react.element), type: "div", key: ".0/.$div0A", ref: null, props: {…}, …}
1: {$$typeof: Symbol(react.element), type: "div", key: ".0/.$div0B", ref: null, props: {…}, …}
2: {$$typeof: Symbol(react.element), type: "div", key: ".$key2/.$div1A", ref: null, props: {…}, …}
3: {$$typeof: Symbol(react.element), type: "div", key: ".$key2/.$div1B", ref: null, props: {…}, …}
4: {$$typeof: Symbol(react.element), type: "div", key: ".$key3/.$div2A", ref: null, props: {…}, …}
5: {$$typeof: Symbol(react.element), type: "div", key: ".$key3/.$div2B", ref: null, props: {…}, …}
6: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key4/.$div3A", ref: null, props: {…}, …}
7: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key4/.$div3B", ref: null, props: {…}, …}
8: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key5/.$div4A", ref: null, props: {…}, …}
9: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key5/.$div4B", ref: null, props: {…}, …}
10: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key6/.$div5A", ref: null, props: {…}, …}
11: {$$typeof: Symbol(react.element), type: "div", key: ".3:$key6/.$div5B", ref: null, props:
可以发现,这个key有个特点,就是如果有key,那么使用key,否则就是索引,然后用/
分割,下一级以.$
开头。另外可以抹平数组。
每下一层会加冒号
自己可以实现下,主要是递归:
function judgeKey ( children, index, keyindex) {
let key = keyindex
if ( children. key) {
key= key+ `$ ${ children. key} `
} else {
key= key+ ` ${ index} `
}
return key
}
function judgeSecond ( key) {
let reg = new RegExp ( '/.' )
return reg. exec ( key)
}
function mapChildren ( children, func) {
let res = [ ]
let index= 0
function mapAll ( children, key) {
for ( let i in children) {
if ( children. hasOwnProperty ( i) ) {
if ( Array. isArray ( children[ i] ) ) {
let newkey = key+ i+ `:`
mapAll ( children[ i] , newkey)
} else {
let newkey = judgeKey ( children[ i] , i, key)
if ( judgeSecond ( newkey) ) {
children[ i] . key = newkey
res. push ( children[ i] )
} else {
let funcres = func ( children[ i] , index)
index++ ;
if ( Array. isArray ( funcres) ) {
let secKey= newkey+ '/.'
console. log ( secKey)
mapAll ( funcres, secKey)
} else {
let mapedkey = judgeKey ( funcres, index, newkey+ '/.' )
funcres. key = mapedkey
res. push ( funcres)
}
}
}
}
}
}
mapAll ( children, '.' )
return res
}
export {
mapChildren as map,
} ;