阅读 257

React 对象源码解析-带你走进高级程序员的世界

React 用了好久,是不是发现自己依然不懂 React 实现原理?只知道它的基本用法,弄不懂 React 内部流程? 本系列文章基于 React v16.8.6,每周分享2-3篇精华文章,每篇文章都是 React 中的一个关键点。欢迎评论交流。

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 },
  // 用于 <Comp ref={r} /> 其中 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) => <div>{props.param.count}</div>,
  // (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,
  
  // const [state, dispatch] = useReducer(reducer, initialArg, init);
  // 它接收一个形如 (state, action) => newState 的 reducer,
  // 并返回当前的 state 以及与其配套的 dispatch 方法
  useReducer: useReducer,
  
  // useRef 返回一个被 Object.seal 处理过的 {current: null} 对象,即对象的 key 属性不可更改
  // 可以用于 ref 访问 dom 如 <div ref={myRef} />
  // 也可用于存值
  // 如 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 <input ref={inputRef} ... />;
  //     }
  // 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 fallback={<Spinner />}><OtherComponent /></Suspense>
  Suspense: REACT_SUSPENSE_TYPE,

  // ------------------------- Element -------------------------------------
  // createElement(type, config, children)
  // 依据 type 来创建一个新的 element,这是 React 使用非常频繁的功能,JSX 就是就是转化成了这个函数
  // <Page ref={r} name="lxfriday" key="1">{childen}</Page>
  // {$$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() 几乎等同于:<element.type {...element.props} {...props}>{children}</element.type>
  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
};
复制代码

React 常见问题

包含 JSX 的代码中为什么一定需要导入 React

来看看编译前的代码

<div ref={r}>
  <span>1</span>
</div>
复制代码

babel 编译之后的代码

React.createElement("div", {
  ref: r
}, React.createElement("span", null, "1"));
复制代码

从编译后的代码可以看到,需要有一个 React对象,JSX 编译之后就是用的 React.createElement 来创建 render 树,所以 React 导入是必须的。

class-component 中定义的 function 为什么要绑定 this

以下为可能的三种情形

  • handleClick 没有绑定 this,打印 undefined,原因是 handleClick 是以赋值的方式被赋予 onClick,所以默认的 this 会失效,类似于 display = foo.display; display(); this 不是 foo
  • handleBindClick1 用箭头函数绑定 this,而且它的 this,不会被变更,打印了 TestThis
  • handleBindClick2,用 bind(this) 绑定上下文,打印了 TestThis
class 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 的原型上

ref 有哪几种形式

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
复制代码
关注下面的标签,发现更多相似文章
评论