React 用了好久,是不是发现自己依然不懂 React 实现原理?只知道它的基本用法,弄不懂 React 内部流程?
本系列文章基于 React v16.8.6
,每周分享2-3篇精华文章,每篇文章都是 React 中的一个关键点。欢迎评论交流。
直接复制的代码,在代码上面打的注释
var React = {
// ----------------------- Children 处理 ---------------------------
// 和 props.children 相关的工具函数
// props.children 不是一个严格的数组,所以添加了类似数组的工具
Children: {
// 类似array.map
map: mapChildren,
// 类似 array.forEach
forEach: forEachChildren,
// 计算 prop.children 的个数,计算直接子节点的个数
// children 为一个时不是数组,就是一个单独的 react.element
// 有多个时,是一个数组,Array.isArray(children) 为 true
// children 个数为0时, children 为 undefined
count: countChildren,
// 将 children 变为数组
// children 为 undefined 变成空数组
// 将单个的变为只有一个元素的数组
// 将是数组的 children 变成数组,差别是数组中每个元素的 key,toArray 处理之后变成 .0 .1 .2 这种标识性的 key
toArray: toArray,
// 如果只有一个直接子节点,则返回它
// 没有直接子节点或者有多个都会报错 React.Children.only expected to receive a single React element child.
only: onlyChild
},
// ---------------------------------------------------------------
// ------------------------- Component -----------------------------
// 是一个函数,执行之后返回 { current: null },
// 用于 其中 r = React.createRef()
createRef: createRef,
// React.Component 最常用
// 它是一个构造函数,同时在它的原型上定义类一些方法
// isReactComponent
// setState
// forceUpdate
Component: Component,
// 继承自 Component,它也是一个构造函数
// 它的原型上有一个属性 isPureReactComponent = true
PureComponent: PureComponent,
// ------------------------------------------------------------------
// React 中跨组件数据传递方案,避免中间元素传递 props 带来的不必要麻烦
// Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
createContext: createContext,
// 创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中
// React.forwardRef 接受渲染函数作为参数。
// React 将使用 props 和 ref 作为参数来调用此函数。此函数应返回 React 节点。
forwardRef: forwardRef,
// ----------------------------- 性能优化 ------------------------
// 配合 Suspense,实现异步加载组件,可以有效减少首次加载的包体积
// 返回一个异步加载的组件
lazy: lazy,
// 优化函数式组件性能的
// 类似于 PureComponent,渲染下面这个组件,不管怎样更改 param 对象,都不会重新render
// const Test = React.memo(
// (props) => {props.param.count},
// (prevProps, nextProps) => true)
memo: memo,
// ---------------------------------------------------------------
error: error,
warn: warn,
// -------------------- React hooks 系列 --------------------
// hooks 可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
// 类似于 setState
// const [a, setA] = useState(aInitialValue) // aInitialValue a 的初始值
// a 是每次 setA 执行之后的最新值,setA 类似于 this.setState,参数可以是函数或者其他
useState: useState,
// 接收一个包含命令式、且可能有副作用代码的函数。
// 赋值给 useEffect 的函数会在组件渲染到屏幕之后执行,
// 也就是可以将改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作放到 useEffect 里面执行
// useEffect 函数需返回一个清除函数,在每次 rerender 之前执行,可以将清除定时器、取消订阅的时间放到返回的函数里面
// const r = useRef()
// useEffect(() => {
// r.current = setTimeout(xx, 1000);
// return () => { clearTimeout(r.current) }
//})
useEffect: useEffect,
//
useReducer: useReducer,
// useRef 返回一个被 Object.seal 处理过的 {current: null} 对象,即对象的 key 属性不可更改
// 可以用于 ref 访问 dom 如
// 也可用于存值
// 如 const r = useRef(),r.current = setTimeout(xx, 1000), clearTimeout(r.current)
useRef: useRef,
// ------------ function component 性能优化工具 ----------------
// 返回一个被缓存的函数,只有在依赖数组中的变量变化的时候才会重新生成一个新的函数,可用于避免子组件重新渲染,提高性能
// 返回自己定义的函数,惰性执行
useCallback: useCallback,
// 惰性求值,它仅会在某个依赖项改变时才重新计算 memoized 值,返回的是自己定义的值
useMemo: useMemo,
// -------------------------------------------------------------
// 在 function component 中直接 useContext(MyContext) 便可以获取到最近的 Provider 的 value
// 不需要 Consumer
useContext: useContext,
// 可以让你在使用 ref 时自定义暴露给父组件的实例值,与 forwardRef 一起使用
// function FancyInput(props, ref) {
// const inputRef = useRef();
// useImperativeHandle(ref, () => ({
// focus: () => {
// inputRef.current.focus();
// }
// }));
// return ;
// }
// FancyInput = forwardRef(FancyInput);
useImperativeHandle: useImperativeHandle,
// 可用于在 React 开发者工具中显示自定义 hook 的标签
useDebugValue: useDebugValue,
// 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
// function useLayoutEffect(effect: EffectCallback, inputs?: InputIdentityList): void;
// function useEffect(effect: EffectCallback, inputs?: InputIdentityList): void;
useLayoutEffect: useLayoutEffect,
// ------------------------------------------------------------
// Symbol(react.fragment),允许你将子列表分组,而无需向 DOM 添加额外节点
Fragment: REACT_FRAGMENT_TYPE,
// Symbol.for('react.profiler') React 性能相关
Profiler: REACT_PROFILER_TYPE,
// StrictMode 不会渲染任何可见的 UI,
// 是一个用来突出显示应用程序中潜在问题的工具, 检查过时的 api,识别不安全的生命周期,检测意外的副作用
StrictMode: REACT_STRICT_MODE_TYPE,
// 配合 lazy 实现懒加载
// 可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件
// const OtherComponent = React.lazy(() => import('./OtherComponent'));
// }>
Suspense: REACT_SUSPENSE_TYPE,
// ------------------------- Element -------------------------------------
// createElement(type, config, children)
// 依据 type 来创建一个新的 element,这是 React 使用非常频繁的功能,JSX 就是就是转化成了这个函数
// {childen}
// {$$typeof: Symbol(react.element), key:"1",props:{name:"lxfriday",children:{...}},ref:null}
createElement: createElementWithValidation,
// cloneElement(element, config, children)
// 以 element 元素为样板克隆并返回新的 React 元素
// 返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,而来自原始元素的 key 和 ref 将被保留。
// React.cloneElement() 几乎等同于:{children}
cloneElement: cloneElementWithValidation,
// 核心语句是下面两条:
// var validatedFactory = createElementWithValidation.bind(null, type);
// validatedFactory.type = type;
// 可以用 createElement 替代
createFactory: createFactoryWithValidation,
// 验证对象是否为 React 元素,返回值为 true 或 false
isValidElement: isValidElement,
// 导出 React 版本
version: ReactVersion,
unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals
};
来看看编译前的代码
<div ref={
r}>
<span>1</span>
</div>
babel 编译之后的代码
React.createElement("div", {
ref: r
}, React.createElement("span", null, "1"));
从编译后的代码可以看到,需要有一个 React对象,JSX 编译之后就是用的 React.createElement 来创建 render 树,所以 React 导入是必须的。
以下为可能的三种情形
handleClick
没有绑定 this,打印 undefined
,原因是 handleClick 是以赋值的方式被赋予 onClick,所以默认的 this 会失效,类似于 display = foo.display; display();
this 不是 foohandleBindClick1
用箭头函数绑定 this,而且它的 this,不会被变更,打印了 TestThishandleBindClick2
,用 bind(this) 绑定上下文,打印了 TestThisclass TestThis extends Component {
handleBindClick1 = () => {
console.log('handleBindClick1', this);
}
handleClick() {
console.log('handleClick', this);
}
handleBindClick2() {
console.log('handleBindClick2', this);
}
render() {
return (
<div>
<button onClick={
this.handleClick}>click not bind </button>
<button onClick={
this.handleBindClick1}>bind 1 click</button>
<button onClick={
this.handleBindClick2.bind(this)}>bind 2 click</button>
</div>
);
}
}
要注意的是,用箭头函数定义的定义的函数式作为 上下文的属性存在,而以普通函数方式定义的函数则是出现在 TestThis 的原型上
function ref
<Page ref={
r => this.page = r}>xxx</Page>
object ref
const r = useRef(); // r = {current: null}
<Page ref={
r}>xxx</Page>
string ref(不推荐)
<Page ref="page">xxx</Page>
this.refs.page.xxx
关注公众号 云影sky,即送技术资料,您的支持是我最大的动力