React - 修改children(上)

React入門,大神輕噴哈^_^
下面的代碼是創建在React 0.14.*版本的
今天在嘗試封裝React component的時候碰到了幾個問題,猜小白們學習React中可能會碰到,我就整理下但願能幫助到小白們。
Keyword: props children cloneElementhtml

  • React父子組件交流通常是用props傳遞,好比:react

const T = React.createClass({
        render() {
            return <h1>{ this.props.text }</h1>
        }
    });
    const B = React.createClass({
        render() {
            return (
                <header>
                    <T text = "level A" />
                    <T text = "level B" />
                </header>
            )
        }
    });
    ReactDOM.render( <B />, document.querySelector('#container'));

運行後天然是輸出下面兩行啦
level A
level Bgit

  • 上面栗子中有兩個子組件包含在<header />中,這裏<header>是咱們熟悉的html標籤,可是假設換作自定義的組件,以下,若是控制< D />的子組件?
    這時候就須要利用this.props的一個重要屬性了:children。github

const C = React.createClass({
        render() {
            return (
                <D>
                    <h1>lever C</h1>
                    <h1>lever D</h1>
                </D>
            )
        }
    });
    const D = React.createClass({
        render() {
            return(
                <div>
                    { this.props.children }
                </div>
            )
        }
    });
    ReactDOM.render( <C />, document.querySelector('#container'));

這裏this.props.children會得到傳入的兩個React element <h1>併成的數組,將其輸出到終端以下:(後邊會繼續介紹這個,先pass)api

clipboard.png

重要的問題來了,那如何控制傳入的elements咧?總不能你給我什麼就呈現什麼吧,好比上面的栗子中我就想把傳入的元素通通變成紅色字體,怎麼作?數組

  1. 給外層div加個ref,如 X , 而後利用this.refs.X.style.color = '#f00'。這種作法只能勉強知足當前作法,很不靈活,一旦要修改的樣式子級沒法繼承就沒用了,因此不可取。學習

  2. 給外層div加個className如 cl ,接着在樣式文件中寫明.cl h1 的樣式。這種作法比上一種方案好一點,可是缺點是隻能應付樣式,而且須要在已知children的標籤類型才行。但假設需求就是給children的全部的文本類標籤添加紅色樣式,但不說明用的是<h1>,<h2>,<h3>仍是<span>等等標籤,那這種作法就須要在樣式文件中窮舉了,固然不可取。字體

  3. 是否能夠直接遍歷this.props.children進而修改屬性呢?this

<D>
        <img ref="img" key="img" src="./logo.png" wen="wen" />    
    </D>

舉上面栗子,輸出的this.props.children,展開後會發現:組件在React中是以一個對象的形式存在,包含了type, ref 和 Key 屬性,還有最重要的props屬性。
props包含了除前邊三個以外的寫在組件上的其餘屬性,包括默認屬性src / alt這些,還有自定義的如這裏的wen屬性。
!這裏注意的是props中不包含{ref,key},下面會說起到。
clipboard.pngspa

  1. 因而考慮經過直接對props進行修改屬性的方法來達到咱們的目的,修改D組件。

const D = React.createClass({
        render() {
            let children = this.props.children.map( (o, i)=>{
                o.props.style = { color: '#f00' };
                return o;
            });
            return (
                <div>
                    { children }
                </div>
            )
        }
    });

運行後驚呆了,報錯了:
Uncaught TypeError: Can't add property style, object is not extensible
意思是說props屬性是不能擴展的,即不能修改。
後面繼續嘗試給o增刪屬性都是會報錯,說明這裏遍歷的每一個對象o都是read only的。
醉了。。。這還怎麼達到咱們的目的咧?

因而乎繼續回頭翻閱React官方文檔,瞄到了React.cloneElement,忽然意識到了什麼,沒錯,正面太鋒芒,咱們繞開它,直接修改對象不行,咱們就拷貝一個本身用。

ReactElement cloneElement(
  ReactElement element,
  [object props],
  [children ...]
)

這裏的element是咱們要拷貝的原對象,在這裏是o。
props可選,當傳入個對象的時候是會和原對象的props進行合併,合併的方式是無則添加,有則覆蓋。
children就是前邊提到的props.children,用來加入子組件的,也是可選,這個栗子中暫時用不到。
因而,嘗試修改代碼以下:

const D = React.createClass({
    render() {
        let children = this.props.children.map( (o, i)=>{
            return React.cloneElement(o, { style: {color:'#f00'} })
        });
        return (
            <div>
                { children }
            </div>
        )
    }
});

運行以後,成了!!!
固然擴展的話能夠根據判斷o.type或者其餘要求來相應的修改屬性,此處統一將全部的<h1>變爲紅色。

  • 不過到這裏還沒結束,雖然效果有了,可是在console中仍是出現了警告:
    Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of D. It was passed a child from C.

這個好理解,就是在遍歷數組的時候須要給每一個元素添加Key屬性。不過問題來了,前邊說,key不算是Props的一員,即不能經過o.props.key=...來添加key。看React.cloneElement的參數列表中只有element, props和children,我又醉了,此次文檔也沒幫到什麼了。
因而乎,看看源碼cloneElement的源碼,不看不知道,一看想給文檔一巴掌,誤導我了!

clipboard.png

是否是恍然大悟了?沒錯,雖然參數名是叫Props,不過實際上React是把ref/key這些屬性也當成Porps的一部分(報錯的信息裏邊也確實說"key" prop),只是在保存的時候會單獨拿出來,爲了和其餘屬性區分開。
在clone的過程當中,對props的遍歷會先單獨把ref和key拿出來判斷,而且不會保存在新的Props中,也就在輸出的時候看到的是分離的。
不過,既然clone的時候props參數包含着Key,那就容易了,修改一行代碼以下。

const D = React.createClass({
    render() {
        console.log(React.cloneElement);
        // 這裏須要注意若是this.props.children只有一個元素,那將不是一個數組,
        // 因此仍是須要提早判斷類型,用Array.isArray(this.props.children)
        let children = this.props.children.map( (o, i)=>{
            return React.cloneElement(o, { style: {color:'#f00'}, key: i })
        });
        return (
            <div>
                { children }
            </div>
        )
    }
});

完成!!!

相關文章
相關標籤/搜索