Vue、React和Angular成为了前端工做者最经常使用的三大框架。分析其源码,对咱们技术的提高有着相当重要的做用,咱们先分析React源码,本篇文章将从其最经常使用的API提及html
一、React源码地址:从github上下载源码前端
二、React官方文档:准备好文档,便于参考react
根据官方文档以及源码,画出一张思惟导图git
这些API将是这篇文章着重介绍的React APIgithub
在React中,咱们常常编写JSX,JSX和JS最大的不一样就是:在JSX中,能够写HTML的代码。那么React是如何解析JSX的呢?这里面就涉及到了JSX向JS的转换,在React中,JSX经过Babel转换成JS去编译。接下来,咱们来研究Babel是如何将JSX转换成JS的。promise
首先推荐一个测试工具:Babel转换工具bash
在JSX中先写一对div标签微信
<div></div>
复制代码
它经过Babel转换成了这样babel
React.createElement("div", null)
复制代码
逐步增长标签内的内容框架
转换前 | 转换后 |
---|---|
<div>我被转换了</div> |
React.createElement("div", null, '我被转换了') |
<div key="1">我被转换了</div> |
React.createElement("div", {key: "1"}, 我被转换了) |
<div key="1" id="2">我被转换了</div> |
React.createElement("div",{key: "1",id: "2"},"我被转换了") |
<div><span>001</span><span>002</span></div> |
React.createElement("div", null, React.createElement("span", null),React.createElement("span",null)) |
经过上面这些状况,能够总结出:
React.createElement
函数建立 ,它接收三个参数(标签名称,标签属性,标签内容)null
,第三个参数会根据标签内容判断,因此可能会出现第四个参数、第五个参数...在React中,咱们常常要封装自定义组件,那么对自定义组件和原生DOM节点的解析有何不一样?
// Test组件时一个自定义组件
function Test() {
return <a>我是一个自定义组件</a>
}
// 使用Test自定义组件
<Test>测试</Test>
复制代码
被Babel编译后
function Test() {
return React.createElement("a", null, "\u6211\u662F\u4E00\u4E2A\u81EA\u5B9A\u4E49\u7EC4\u4EF6");
}
React.createElement(Test, null, "\u6D4B\u8BD5");
复制代码
能够看到:
React.createElement
函数中的第一个参数是一个变量,而不是像上面同样是一个字符串,这是由于Babel区分了自定义组件和原生DOM节点在React中,常用JSX,咱们也首先要了解JSX向JS转换的原理,其次才能作其余部分的事情。
咱们要知道的第一个API就是ReactElment,它的源码的文件名是ReactElement.js。上面的思惟导图就是这个.js文件大概所要阐述的,中心主题是咱们所说的ReactElement,第二层节点是全部的方法,第三层节点是方法所接收的参数,第四层及之后是参数上的属性,每一个方法最后会返回一个方法,虚线是最后返回ReactElemnt方法的部分。有了这样一个思路之后,再看源码就不会感受到那么难了。
我摘取了如下几个部分
1.这里面使用了不少Object构造函数中的方法,好比getPropertyDescriptor()
、defineProperty()
等等,这须要咱们去了解这些方法的用途。
2.在createElemnt
方法中,有这样一段代码
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];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
复制代码
createElement
方法中会接收三个参数,这部分代码是处理第三个参数children
的,咱们在3.1中介绍JSX
向JS
中转换说到的createElement
方法其实就是它,createElement
方法接收的参数个数由父节点的内容所控制(参见3.1),因此咱们看到的childrenLength
的计算-2
,就是减掉了这个方法接收的前两个参数,而后呢,根据它的长度判断chilren
的值,最后传给了props
的children
属性
3.对defaultProps的处理
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
复制代码
这部分也是在createElement
方法中处理的,它是对第一个参数type
中的defaultProps
属性的处理,在咱们本身封装组件时,可能会让一些属性有默认值,在父组件中使用时,会判断父组件是否给子组件传值,若是没有,就会使用defaultProps
中定义的默认值
4.ReactElement方法接收了七个参数,并最终返回了element。剩下的部分大多和这些思想一致,就不一一累述。
学习源码是一个枯燥的过程,而这仅仅是一个API介绍,是一个开始,咱们要作的就是学习源码的思考问题的方式等,让本身思考问题更加全面,学会写更优秀的代码。我同时也推荐先画思惟导图这种方式,理解,再深刻研究每一个方法的用途,这么写代码的目的。
Component源码是在ReactBaseClasses文件中,一样在这个文件中存在的还有一个PureComponent源码,最终在文件中export {PureComponent, Component}
。这部分代码不是很长,咱们依然挑出重点的部分进行分享:
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');
};
复制代码
这部分代码就是咱们经常使用的this.setState
方法的源码部分,它是存在于Component的原型链上的方法,接受了两个参数,第一个参数是要更新state的部分,第二个参数是一个回调函数,走进函数,首先是要判断partialState
的类型,若是类型不正确,将会报错;正式更新state的部分是this.updater.enqueueSetState(this, partialState, callback, 'setState')
,这里面调用了updater
中的enqueueSetState
方法,它接收了四个参数,那么它是如何更新state的呢?咱们先留一个悬念...
一、ref
ref的使用有三种方式:
① stringRef
② methodRef(function)
③ createRef
import React, {Component} from 'react';
export default class Ref extends Component {
constructor (props) {
super();
this.objRef = React.createRef()
this.state = {
ref: '初始化'
}
}
componentDidMount() {
setTimeout(() => {
this.refs.stringRef.textContent = '我加载成功了'
this.methodRef.textContent = '我也加载成功了'
this.objRef.current.textContent = '我一样加载成功了'
}, 2000)
}
render() {
const { ref } = this.state;
return (
<div>
<h3 ref="stringRef">{ref}</h3>
<h3 ref={ele => (this.methodRef = ele)}>{ref}</h3>
<h3 ref={this.objRef}>{ref}</h3>
</div>
)
}
}
复制代码
二、forward-ref
forward-ref的出现是为了解决HOC组件传递ref的问题
import React, {Component} from 'react';
const Target = React.forwardRef((props, ref) => {
return (
<input ref={ref} />
)
})
export default class Forward extends Component {
constructor(props) {
super();
this.ref = React.createRef();
this.state = {
}
}
componentDidMount(){
this.ref.current.value = 'forward ref';
}
render() {
return <Target ref={this.ref} />
}
}
复制代码
对于ref的三种使用方式,在源码中有所体现的是第三种。在源码中,有一个createRef函数,它声明了refObject变量,里面存在current属性,默认值为空,最后return refObject,在咱们使用的时候,current的值就是当前使用的节点名称。
forward-ref返回了一个对象,里面存在$$typeof和render函数。对于forward-ref的使用,它解决了HOC组件传递ref的问题,使用的方法如上面第二段代码所示。若是使用stringRef没法将ref做为参数传递,因此有了createRef方法
// 子组件
import React, {Component} from 'react';
import PropTypes from 'prop-types';
export default class ContextChild extends Component {
render() {
return (
<div>
<h3>childContext: {this.context.value}</h3>
</div>
)
}
}
ContextChild.contextTypes = {
value: PropTypes.string
}
// 父组件
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import ContextChild from './context-child';
export default class Context extends Component {
constructor (props) {
super();
this.state = {
childContext: 'childContext',
}
}
getChildContext() {
return {value: this.state.childContext}
}
render() {
const { childContext } = this.state;
return (
<div>
<div>
<input
value={childContext}
onChange={e => {this.setState({childContext: e.target.value})}}
/>
</div>
<ContextChild />
</div>
)
}
}
Context.childContextTypes = {
value: PropTypes.string
}
复制代码
Input
输入框值改变时,子组件中获取的
this.context.value
也会随之改变,这里面,须要注意的是要规定context的类型,若是父子组件context类型不统一,是不会得到上面的结果的
import React, {Component} from 'react';
import PropTypes from 'prop-types';
const { Provider, Consumer } = React.createContext()
export default class Context extends Component {
constructor (props) {
super();
this.state = {
create: 'create'
}
}
render() {
const { childContext, create } = this.state;
return (
<div>
<div>
<input
type="text"
value={create}
onChange={e => {this.setState({create: e.target.value})}}
/>
</div>
<Provider value={this.state.create}>
<Consumer>{value => <p>create: {value}</p>}</Consumer>
</Provider>
</div>
)
}
}
Context.childContextTypes = {
value: PropTypes.string
}
复制代码
Provider和Consumer组件配合使用,Consumer组件能够得到Provider组件上的value属性的值,当input值改变时,Consumer组件里面的值随之改变
context部分只有createContext的源码,它接收了defaultValue和calculateChangedBits(计算新老context的API变化的方法)两个值,最后返回了context对象,下面分析context对象这一部分的源码
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
_currentValue: defaultValue,
_currentValue2: defaultValue,
// Used to track how many concurrent renderers this context currently
// supports within in a single renderer. Such as parallel server rendering.
_threadCount: 0,
// These are circular
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
复制代码
上面代码中
I. _currentValue: 记录Provider组件上value属性的值
II. context对象里面拥有Provider、Consumer
III. _context: context中的context指的是上面的context对象
IV. context.Consumer等于自己的context对象,因此在使用Consumer组件时,可直接获取context对象中_currentValue的值
// suspenseDemo.jsx
import React, { Suspense, lazy } from 'react'
const LazyComp = lazy(() => import('./suspenseChild.jsx'))
let data = ''
let result = ''
function requestData() {
if (data) return data
if (result) throw result
result = new Promise(resolve => {
setTimeout(() => {
data = 'success'
resolve()
}, 4000)
})
throw result
}
function SuspenseComp() {
return <p>{requestData()}</p>
}
export default SuspenseDemo => (
<Suspense fallback="loading data">
<SuspenseComp />
<LazyComp />
</Suspense>
)
// suspenseChild.jsx
import React, { Component } from 'react';
export default () => {
return <p>child</p>
}
复制代码
效果图:
Suspense组件在其下方组件返回promise结果以前,都展现fallback函数中的值,包裹在suspense中用lazy加载的组件也会在promise结果返回后与异步组件一同加载。Suspense是一个常量,lazy在react中接收一个方法,返回Thenable,Thenable是promise对象,最终返回LazyComponent
export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
let lazyType = {
$$typeof: REACT_LAZY_TYPE,
_ctor: ctor,
// React uses these fields to store the result.
_status: -1,
_result: null,
};
return lazyType;
}
复制代码
返回的LazyComponent中,存在lazyType对象,其中的_ctor属性为它接收的方法,_status属性是记录Thenable对象的状态,_result为返回的结果。
在react的官方网站上一个useState的实例
import React, { useState } from 'react';
const State = () => {
// 声明一个新的叫作 “count” 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default State
复制代码
效果图
hooks是react16.8版本新增的特性,它可使咱们在不编写class的状况下使用state或者其余的React特性。
在使用的时候,咱们要先引入使用的特性,像这样:
import React, { useState } from 'react';
而后根据咱们想要实现的功能,去选择对应的hook功能API,hook的源码先定义了一个resolveDispatcher函数方法,该方法返回dispatcher,接下来定义每个hook的API并导出,设置其API接收的参数,并有对应的返回
这篇文章主要讲解了部分React的基础API以及它的使用方法和部分源码解析,并无深刻。接下来,咱们会一点一点深刻react,接下来关于react的文章:
上述文章若有不对之处,还请你们指点出来,咱们共同进步~
最后,分享一下个人微信公众号~