Enzyme dive() 和 shallow() 的區別

ShallowWrapper API 中有兩個易混的 .dive().shallow(),調用它們都會返回 ShallowWrapper,但他們在使用場景和執行過程當中都有所區別。html

API 定義

  • .dive([options]) => ShallowWrappernode

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

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

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

源碼解析

  • .dive() 源碼github

    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() 源碼web

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

咱們能夠將 .dive() 源碼稍做變化:app

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 提到dom

.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 到高階組件包裹的自定義組件中(後文有例子)。this

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()

相關文章
相關標籤/搜索