1

ShallowWrapper API 中有两个易混的 .dive().shallow(),调用它们都会返回 ShallowWrapper,但他们在使用场景和执行过程中都有所区别。

API 定义

  • .dive([options]) => ShallowWrapper

    Shallow render the one non-DOM child of the current wrapper, and return a wrapper around the result.

    non-DOM child 指的是 非 dom 的 reactElement(不能是 div span 等)
  • .shallow([options]) => ShallowWrapper

    Shallow renders the current node and returns a shallow wrapper around it.

源码解析

  • .dive() 源码

    dive(options = {}) {
      const adapter = getAdapter(this[OPTIONS]);
      const name = 'dive';
      return this.single(name, (n) => {
        if (n && n.nodeType === 'host') {
          throw new TypeError(`ShallowWrapper::${name}() can not be called on Host Components`);
        }
        const el = getAdapter(this[OPTIONS]).nodeToElement(n);
        if (!isCustomComponentElement(el, adapter)) {
          throw new TypeError(`ShallowWrapper::${name}() can only be called on components`);
        }
        const childOptions = makeInheritedChildOptions(this, options);
        return this.wrap(el, null, childOptions);
      });
    }
  • .shallow() 源码

    shallow(options = {}) {
      return this.single('shallow', (n) => {
        const childOptions = makeInheritedChildOptions(this, options);
        return this.wrap(getAdapter(this[OPTIONS]).nodeToElement(n), null, childOptions);
      });
    }

我们可以将 .dive() 源码稍作变化:

dive(options = {}) {
  return this.single('dive', (n) => {
    // 错误判断-start
    if (n && n.nodeType === 'host') {
      throw new TypeError(`ShallowWrapper::${name}() can not be called on Host Components`);
    }
    const el = getAdapter(this[OPTIONS]).nodeToElement(n);
    const adapter = getAdapter(this[OPTIONS]);
    if (!isCustomComponentElement(el, adapter)) {
      throw new TypeError(`ShallowWrapper::${name}() can only be called on components`);
    }
    // 错误判断-end
    const childOptions = makeInheritedChildOptions(this, options);
    return this.wrap(getAdapter(this[OPTIONS]).nodeToElement(n), null, childOptions);
  });
}

通过对比变体后的 .dive().shallow() 对比,如果去掉错误判断部分,我们可以看到,.dive() 的逻辑和 .shallow() 一样。通常,.dive() 配合高阶组件一起使用,如在 Enzyme 仓库这个 issue 中 Documentation does not make it clear when to use .dive() vs .shallow()@Jordan Harband 提到

.dive() is sugar for "throw if there's more than one child, throw if that child isn't a custom component, call .shallow on it". It was a common enough pattern that it warranted first-class inclusion.
In particular, the mantra i often use is "when shallow rendering, one .dive() per HOC"

如果仅从功能来看,能用 .dive() 的场景同样可以使用 .shallow() ,且结果一样。

.dive() 内部做了更加严格的类型判断(不能是 Host Components,且必须是自定义的 components。对 web 而言,div 等原生 html DOM 元素,以及 null 或者 react 内建组件 <Fragment> 都不行),并且,在高阶组件中使用,语义也更加友好:dive 到高阶组件包裹的自定义组件中(后文有例子)。

With respect to renderers there are two types of react components:
Host Components: Host components are platform-specific components, such as <div> or a <View> and they run platform-specific code such as mounting, updates, and unmounting of DOM/Native view. They typically begin with lower-case in the case of ReactDom.
Composite Components: Composite components are user-defined components such as <MyButton> or <Content> and they behave the same way with all renderers. React will calls methods, such as render() and componentDidMount(), on the user-supplied composite components.

使用场景

宿主对象类型

.dive() 只能用于非 DOM 的 wrapper,而 .shallow() 没有此限制,如果成功输出 ShallowWrapper,结果都一样。看下面代码:

test('试验', () => {
  class Bar extends React.Component {
    render() {
      return (
        <div>
          <div className="in-bar" />
        </div>
      );
    }
  }

  class Foo extends React.Component {
    render() {
      return (
        <div>
          <Bar />
        </div>
      );
    }
  }

  const wrapper = shallow(<Foo />);

  // 因为浅渲染,Bar 在当前 wrapper 中只会渲染成 <Bar />,所以找不到其包含的 '.in-bar'
  expect(wrapper.find('.in-bar').length).toBe(0);

  // 这里找到的 Bar 是 <Bar />
  expect(wrapper.find('Bar').length).toBe(1);

  // 对 '<Bar />' 使用 dive() 后,将会渲染它,然后就可以找到 '.in-bar'了
  expect(wrapper.find('Bar').dive().find('.in-bar').length).toBe(1);
  // 这里可以使用 shallow() 效果相同
  expect(wrapper.find('Bar').shallow().find('.in-bar').length).toBe(1);

  // dive() 只能应用于 非 DOM
  console.log(wrapper.find(Bar).dive().find('.in-bar').dive().debug()); // 报错,不能对 dom 使用
  console.log(wrapper.find(Bar).dive().find('.in-bar').shallow().debug()); // 正常输出 html
});
.dive() only works on a wrapper around a single element from a custom component. If you want to shallow on an HTML element, or multiple, you'd need .shallow(). I think the use cases are rare, but they exist. by @Jordan Harband

配合高阶组件使用

const withHOC = Component => props => (
  <Component testHOCProp="come from HOC" {...props} />
);
class Bar extends React.Component {
  render() {
    return (
      <div>
        <div className="in-bar" />
      </div>
    );
  }
}
class EnzymeDive extends React.Component {
  shouldComponentUpdate() {
    console.log(1111);
  }
  render() {
    return (
      <div>
        Enzyme Dive
        <Bar />
      </div>
    );
  }
}

test('Enzyme .dive() with HOC', () => {
  const wrapper = shallow(<EnzymeDive />);
  wrapper.dive().setProps();

  console.log('====wrapper.debug()====', wrapper.debug());
  console.log('====wrapper.dive().debug()====', wrapper.dive().debug());
});

test('Enzyme .shallow() with HOC', () => {
  const wrapper = shallow(<EnzymeDive />);
  wrapper.shallow().setProps();

  console.log('====wrapper.debug()====', wrapper.debug());
  console.log('====wrapper.dive().debug()====', wrapper.shallow().debug());
});

两个测试用例 Enzyme .dive() with HOCEnzyme .shallow() with HOC 输出的结果都一样,且对于 EnzymeDive 组件而言,测试覆盖率都达到了 100%。其中

  • wrapper.debug() 的输出结果为:
<EnzymeDive testHOCProp="come from HOC" />
  • wrapper.dive().debug()wrapper.shallow().debug() 的输出结果为:
<div>
  Enzyme Dive
  <Bar />
</div>

可以看到 .dive().shallow() 都 "unwrap" 了高阶组件("unwrap" here just means "shallow-render one level deeper" )。

image.png

测试源码可以在这里找到。

结论

如果宿主对象不是 div 等原生 html DOM 元素,使用哪个功能一样。但如 Enzyme(3.11.0)源码注释所写,如果是高阶组件,推荐使用 .dive(),其他场景,使用 .shallow()

参考资料

Documentation does not make it clear when to use .dive() vs .shallow()


L小庸
871 声望592 粉丝