React进阶

  1. ReactDOM.createPortal(child, container):传送门。将子节点渲染到存在于父组件以外的 DOM 节点,即将组件插入到任意DOM节点;
    • child:任何可渲染的 React 子元素,如一个元素、字符串、碎片;
    • container:DOM元素;
    • 注:它并不会直接操作DOM,而是返回一个React组件,必须通过render渲染才有效。
    class Dialog extends React.Component {
        constructor(props) {
            super(props)
            // 在body下为自己添加一个父级,其实可以直接把body作为自己的父级
            this.node = document.createElement('div')
            document.body.appendChild(this.node)
        }
        componentWillUnmount() {
            // 把自己移除掉
            document.body.removeChild(this.node)
        }
        render() {
            return ReactDOM.createPortal(
                <div className="dialog">
                    <h1>Dialog</h1>
                    { this.props.children }
                </div>,
                this.node
            )
        }
    }
    
    export default function Home(props) {
        let [ visible, showDialog ] = useState(false)
        const remove = ev => {
            showDialog(!visible)
        }
        let span = <span name="Machel">Vockty</span>
        return (<div id="home">
            <button onClick={() => showDialog(!visible)}>控制Dialog</button>
            {
                visible && <Dialog />
            }
        </div>)
    }
    
    通常讲,组件只能装配到最近的父元素上,而对于对话框、提示框等组件,需要跳出其容器;Portal 可以把组件放置到DOM树的任何地方。
  2. React.cloneElement(element, [props], [...children]) 克隆组件或DOM元素,返回一个新的组件。
    • element -克隆的母体组件,React组件 或 原生DOM
    • props -可选,配置elementprops
    • children -可选,为新的React元素添加新的children,取代从母体中克隆而来的children
      export default function Home(props) {
          const span = <span name="Machel">Vockty</span>
          return (<div>
              {
                  React.cloneElement(span, { name: 'Jacky' }, <em>EMChild</em>)
              }
          </div>)
      }
      // 渲染结果 ==>
      <div>
          <span name="Jacky">
              <em>EMChild</em>
          </span>
      </div>
      
  3. react-transition-group React官方维护的动画库,包括三个核心(组件):Transition、CSSTransition、TransitionGroup
  4. CSS模块化
    import style from './index.module.css'
    <img className={style.img} />
    

性能优化

PureComponent

  1. PureComponent 是内部定了 shouldComponentUpdate 生命周期的组件;
  2. PureComponent是一个浅比较,所以务必注意:
    • 确保数据类型是值类型
    • 如果是引用类型,确保地址不变,同时不应该有深层次数据变化
  3. 低效率更新
    import React, { Component } from 'react'
    
    class Title extends Component {
        constructor(props) { super(props) }
        render() {
            console.log('Title update')
            return <div>title: {this.props.title}</div>
        }
    }
    class Count extends Component {
        constructor(props) { super(props) }
        render() {
            console.log('Count update')
            return <div>Count: {this.props.count}</div>
        }
    }
    
    export default class App extends Component {
        constructor(props) {
            super(props)
            this.state = { title: "标题", count: 0 }
    
            // 每隔2s只更新 count 属性
            setInterval(() => {
                this.setState((preState, preProp) => ({
                    count: ++preState.count
                }))
            }, 2000)
        }
        render() {
            return (<div>
                <Title title={this.state.title} />
                <Count count={this.state.count} />
            </div>)
        }
    }
    
    • App组件中每隔 2s 只更新 count 属性,虽然子组件Title中只是用了 title 属性,并没有使用 count 属性,但依然会被更新!
  4. 使用生命周期函数 shouldComponentUpdate() 判断两次 title 是否相同,从而决定是否更新;
    class Title extends Component {
        shouldComponentUpdate(nextProps) {
            return nextProps.title !== this.props.title;
        }
    }
    
  5. PureComponent 可以简化 shouldComponentUpdate() 的判断
    import React, { Component, PureComponent } from 'react'
    class Title extends PureComponent {
        // 无需任何改变
    }
    

React.memo

React.memo 是一个高阶组件,让函数式组件也拥有 PureComponent 的功能;

    const Title = React.memo(props => {
        return <p>title: {props.title}</p>
    })

组件复合

组件复合:类似于Vue中的插槽,复用组件
React官方:任何一个能用组件继承实现的,都能用组件复合实现;
React组件包裹的子元素会被保存在 props.children 中,这样在组件内就可以处理渲染这些子元素。

  1. React.Children 处理 props.children 的工具。
    props.children 的值有三种可能:

    • undefined -当前组件没有子节点
    • object - 有一个子节点
    • array -多个子节点

    使用 React.Children.map 来遍历子节点,就不用担心 props.children 的数据类型了。

    function Dialog(props) {
        return (<ul>
            { 
                React.Children.map(props.children, child => {
                    return <li>{ child }</li>
                })
            }
        </ul>)
    }
    export default function Composition() {
        return (<div>
            <Dialog>
                <h2>匿名插槽</h2>
                <p>复合的标签</p>
            </Dialog>
        </div>)
    }
    

    这就像是Vue 上的 匿名插槽

  2. 具名插槽不能使用 React.Children 处理

    • 通过 props 直接传递
      function Dialog(props) {
          return <div>{ props.btn }</div>
      }
      export default function Composition() {
          const btn = <button onClick={() => alert('具名插槽')}></button>
          return (<div>
              <Dialog color="pink" btn={btn}></Dialog>
          </div>)
      }
      
    • 真正意义上的具名插槽
      function Dialog(props) {
          return (<div>
              { props.children.btn }
              { props.children.count }
          </div>)
      }
      export default function Composition() {
          return (<div>
              <Dialog>
                  {{
                      btn: <div>我是具名插槽-BTN</div>,
                      count: <div>我是具名插槽-COUNT</div>
                  }}
              </Dialog>
          </div>)
      }
      
  3. 匿名插槽和具名插槽同时处理

    function Dialog(props) {
        let compArr = []
        props.children.forEach(item => {
            if(item.$$typeof) { // 匿名
                compArr.push(item)
            } else {   // 具名
                for(let attr in item) {
                    compArr.push(item[attr])
                }
            }
        })
        return <div>{ compArr }</div>
    }
    export default function Composition() {
        return (<div>
            <Dialog>
                {{
                    btn: <div>我是具名插槽-BTN</div>,
                    count: <div>我是具名插槽-COUNT</div>
                }}
                <p>这是匿名插槽-1</p>
                <h5>这是匿名插槽-2</h5>
            </Dialog>
        </div>)
    }
    

高阶组件

高阶组件:为了提高组件的复用率,抽离具有相同逻辑/展示的组件,扩展组件的功能。
高阶组件其实是一个工厂函数,接收一个组件,返回一个新的组件,这个新组件可以对属性进行包装,也可以重写部分生命周期;

  1. 简单使用
    • HOCompt.js
      const withLearnReact = (Compt) => {  // 工厂函数
          const NewCompt = (props) => {
              return <Compt {...props} name="高阶组件" />
          }
          return NewCompt
      }
      class HOC extends Component {
          render() {
              return (<div>
                  <p>{this.props.title}</p>
                  <p>{this.props.name}</p>
              </div>)
          }
      }
      export default withLearnReact(HOC)
      
    • App.js
      render() {
          return <div><HOCompt title="App use" /></div>
      }
      
  2. 链式调用
    1. 如果是纯展示的组件,则返回一个函数式组件;如果需要重写生命周期,则返回类组件;
    2. HOCompt 组件中,新添加一个返回类组件的函数,并重写类组件的生命周期方法;
      const withLifeCycle = (Compt) => {
          class NewCompt extends Component {
              componentDidMount() {
                  console.log('重写生命周期componentDidMount')
              }
              render() {
                  return <Compt {...this.props}></Compt>
              }
          }
          return NewCompt
      }
      export default withLifeCycle(withLearnReact(HOC))  // 链式调用
      
    3. withLearnReact(HOC) 返回一个函数式组件,传递了 nametitle ,该组件作为 withLifeCycle() 的参数,返回一个重写了生命周期方法的类组件。
  3. 装饰器的方式实现链式调用,更加优雅的方式。结合Ant Design的按需加载
    create-react-app 创建的React项目,借助 react-app-rewired 取代 react-scripts,可以扩展webpack的配置,实现Ant Design的按需引入
    npm i antd -S  // Ant Design
    // babel-plugin-import 用于按需加载组件代码和样式的 babel 插件
    npm i react-app-rewired customize-cra babel-plugin-import -D
    
    // 支持装饰器配置
    npm i @babel/plugin-proposal-decorators -D
    
    • 在根目录下新建config-overrides.js,添加如下内容:
      const { override, fixBabelImports, addDecoratorsLegacy, addBabelPlugins } = require('customize-cra');
      module.exports = override(
          fixBabelImports('import', {
              libraryName: 'antd',
              libraryDirectory: 'es',
              style: 'css'
          }),
          // 配置装饰器
          addDecoratorsLegacy()
      
          //配置支持高阶组件的装饰器
          //addBabelPlugins(['@babel/plugin-proposal-decorators', { legacy: true }])
      )
      
    • 修改 package.jsonscripts 节点
      "start": "react-app-rewired start",
      "build": "react-app-rewired build",
      "test": "react-app-rewired test",
      "eject": "react-app-rewired eject"
      
    • 不需要再引入全量的antd.css,直接引入需要的组件即可,会自动加载组件相关的CSS
      import { Button } from 'antd'
      <Button type="primary">Primary</Button>
      
  4. HOCompt.js
        const withLearnReact = (Compt) => {
            const NewCompt = (props) => {
                return <Compt {...props} name="高阶组件" />
            }
            return NewCompt
        }
        const withLifeCycle = Compt => {
            return class extends Component {
                componentDidMount() {
                    console.log('重写生命周期componentDidMount')
                }
                render() {
                    return <div className="border"><Compt {...this.props}></Compt></div>
                }
            }
        }
        @withLearnReact
        @withLifeCycle
        class HOC extends Component {
            render() {
                return (<div>
                    <p>{this.props.title}</p>
                    <p>{this.props.name}</p>
                </div>)
            }
        }
        export default HOC
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容

  • 40、React 什么是React?React 是一个用于构建用户界面的框架(采用的是MVC模式):集中处理VIE...
    萌妹撒阅读 1,005评论 0 1
  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,428评论 1 33
  • 今天的React题没有太多的故事…… 半个月前出了248个Vue的知识点,受到很多朋友的关注,都强烈要求再出多些R...
    浪子神剑阅读 10,070评论 6 106
  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 8,051评论 2 35
  • 今天我批准自己随心所欲的脑袋里想到什么就写下什么。 其实平时不经意间或者和朋友聊天时,说的话都觉得十分的精彩,万分...
    往日诙谐阅读 319评论 2 5