抓住它,八阿哥

Yangfan發表於2019-04-18

哈哈,和我一起踏上 bug 尋找之旅吧

前言

在最近的一個專案中,用到了 ant-design 的 輪播圖元件 Carousel,發現無法按照預期給每個 slide 加樣式(內聯樣式),後來發現是 ant-design 的輪播圖元件引用的三方庫 react-slick,通過原始碼查詢,終於發現是程式碼把內聯樣式重寫了

情景再現

按照 ant-design 的官方示例,把demo拷貝過來

import { Carousel } from 'antd';

function onChange(a, b, c) {
  console.log(a, b, c);
}

ReactDOM.render(
  <Carousel afterChange={onChange}>
    <div><h3>1</h3></div>
    <div><h3>2</h3></div>
    <div><h3>3</h3></div>
    <div><h3>4</h3></div>
  </Carousel>,
  mountNode
);
複製程式碼

一切正常。然後按業務改動程式碼,加入背景圖

import urlBg001  from "./images/urlBg001.png";
import urlBg002 from "./images/urlBg002.png";
import urlBg003 from "./images/urlBg003.png";

ReactDOM.render(
  <Carousel afterChange={onChange}>
    <div style={{ backgroundImage: `url(${urlBg001})` }}><h3>1</h3></div>
    <div style={{ backgroundImage: `url(${urlBg002})` }}><h3>2</h3></div>
    <div style={{ backgroundImage: `url(${urlBg003})` }}><h3>3</h3></div>
  </Carousel>,
  mountNode
);
複製程式碼

然而,沒有任何效果(不顯示背景圖)

思考問題所在

為何會這樣呢

首先,我以為是圖片路徑的問題,做了個驗證,發現圖片正常顯示


ReactDOM.render(
  <Carousel afterChange={onChange}>
    <div><img src={urlBg001} /></div>
  </Carousel>,
  mountNode
);

複製程式碼

然後,我就懷疑是不是 Carousel 這個元件對插槽做了限制,進行了進一步的驗證


ReactDOM.render(
  <Carousel afterChange={onChange}>
    <div id="test-slide" style={
        { 
            backgroundImage: `url(${urlBg001})`,
            color:"#f02",
            width:"50px",
        }}>1</div>
  </Carousel>,
  mountNode
);

複製程式碼

結果,沒有任何效果,開啟控制檯,找到 id 是 test-slide 的那個元素,發現渲染的結果是下面這樣:

<div id="test-slide" tabindex="-1" style="width: 100%; display: inline-block;">1</div>
複製程式碼

style 屬性被重寫了,我自己加的 style 不見了,更加確信了是 Carousel 元件內部搞的鬼

開啟 github 扒 ant-design 原始碼,沒有發現對 style 屬性做任何更改,然後在頭部找到該元件其實是引入一個 react-slick,然後包裝了下

// https://github.com/ant-design/ant-design/blob/master/components/carousel/index.tsx#L23

const SlickCarousel = require("react-slick").default;

export default class Carousel extends React.Component<CarouselProps, {}> {
  // 部分程式碼省略 ...
  renderCarousel = ({ getPrefixCls }: ConfigConsumerProps) => {
    // 部分程式碼省略 ...
    return (
      <div className={className}>
        <SlickCarousel ref={this.saveSlick} {...props} />
      </div>
    );
  };

  render() {
    return <ConfigConsumer>{this.renderCarousel}</ConfigConsumer>;
  }
}
複製程式碼

真相慢慢的付出了水面,再次扒 react-slick 的原始碼,終於功夫不負有心人,在這個檔案裡發現了問題的源頭

// https://github.com/akiran/react-slick/blob/master/src/slider.js#L184

React.cloneElement(children[k], {
  key: 100 * i + 10 * j + k,
  tabIndex: -1,
  style: {
    width: `${100 / settings.slidesPerRow}%`,
    display: "inline-block"
  }
})

複製程式碼

直接將子元素childrenstyle 屬性覆蓋了

最後在 react-slick 找到了相關 issue,然額這個 issue 仍然是 open 狀態

在 PR 中找到 pull#1372,有人嘗試修復過,但是 PR 又關了,不知道什麼原因

解決方案

由於不知道原始碼作者為何要這麼設定,我只能先找其他方法解決

  • 方法一 多巢狀一層,只要不是在第一層元素上操作就行
ReactDOM.render(
  <Carousel>
    <div>
      <div style={{ backgroundImage: `url(${logo})` }}>1</div>
    </div>
  </Carousel>,
  mountNode
);
複製程式碼
  • 方法二

用其他方式達成目的

ReactDOM.render(
  <Carousel>
    <img src={logo} alt="001"/>
  </Carousel>,
  mountNode
);
複製程式碼

總結

  • 找這個 bug 還是挺費時間的,不過,通過這次 bug 之旅,學到一定要用科學的方法,精準定位問題來源,多做空白試驗參照,進行對比,這樣才能更快的解決問題
  • 一定要多看原始碼,可以學到很多技巧

備註

可以點選下面的連結檢視 “情景重現” 和元件原始碼

相關文章