返回 blog
2020年12月24日
5 分钟阅读

react 进阶

组件懒加载

使用 React.lazy 和 React.Suspense 实现组件的懒加载

import React, { Suspense } from 'react';
const component = React.lazy(()=>import('./someComponent.js'))

const T = () => {
	return (
  	<Suspense fallback={<div>loading...</div>}>
    	<component />
    </Suspense>
  )
}

Context 全局上下文

  • 注意: 被注入context的组件,当context改变时,组件就会重新渲染,无论组件是否使用了context
  • 注意: context中的value是通过 Object.is 比较新旧值 (所以当value是个对象时,初始化的时候需要传state,而不是直接在value中写个对象)
  • 使用context前的考虑: 那种真的会被全局使用到的属性, 如主题这种,才有必要通过context传递

使用方式:

  1. 创建context
const MyContext = React.createContext(defaultValue);
  1. 挂载context
<Mycontext.Provider value={/* someValue */}>
  {/*
  	消费组件
  */}
</Mycontext.Provider>
  1. 在消费组件中使用
// 函数组件中使用useContext使用
import MyContext from './xx.js'
import { useContext } from 'react'

const ctx = useContext(MyContext) // 获取到了context中的value

// 在类组件中使用 Context.Consumer 或 contextType 
// ------------- contextType
class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
  }
  render() {
    let value = this.context;
    /* 基于 MyContext 组件的值进行渲染 */
  }
}
MyClass.contextType = MyContext;

// ------------ Context.Consumer
<MyContext.Consumer>
  {value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>

Ref转发

通俗来说: 把一个组件中的ref转发给他的父组件。 有时候我们需要控制button的焦点,这个时候或许就能用到ref转发

  • 注意: 第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref
const FancyButton = React.forwardRef((props, buttonRef) => (
  <button ref={buttonRef} className="FancyButton">
    {props.children}
  </button>
));

// 你可以直接获取 button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

Fragments

类似vue中的template, 无意义的节点,不会渲染到dom树中 新语法: <></> 

高阶组件

目前我还没用自己写过高阶组件。 高阶组件就是传入一个组件,返回一个新组件。高阶组件可以对组件进行一系列的处理

  • 注意: 在高阶组件中,不要改变传入的参数组件

其余待我使用之后再补充

深入JSX

动态组件

在vue中,动态组件是用 <component is="xx"> 在react中, 动态组件很简单

import A from './a.js'
import B from './b.js'

const C = (props) => {
	const components = {
  	A,
    B
  }
  const DynamicComponent = components[props.type]
  return (
  	<DynamicComponent />
  )
}

属性默认

react与vue相同,props未赋值表示true

// 两个等价
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />

插槽

习惯了这个称呼,但其实在react中没有插槽的说法 在vue中,插槽是这样用的

// A.vue
<template>
  <div>
    <div>
      <span>这是一些文字</span>
      <slot name="icon"></slot>  // 具名插槽
    </div>
    <div>
      <slot></slot> // 默认插槽
    </div>
  </div>
</template>

<script>
export default {
};
</script>

// B.vue
<template>
	<div v-for="i in 10">
  	<A key="i">
      <span slot="icon">图标</span>
      <div>插入到默认插槽的</div>
  	</A>
  </div>
</template>
<script>
import 'A' from './A.vue'
export default {
  components: { A }
};
</script>

在react中,可以这样

const SlotComp = (props) => {
  const { children } = props // children是一个数组
  const Icon = children.xxxx // 伪代码,从props中取Icon
  const Default = children.yyy
	return (
  	<div>
      <div>
        <span>这是一些文字</span>
        {Icon}  
      </div>
      <div>
        {Default} 
      </div>
  </div>
  )
}

import SlotComp from './x.js'
const M = () => {
	return (
		<>
    	<SlotComp>
      	<icon name='icon' />
      	<div>默认的一些内容xxx</div>
    	</SlotComp>
    	<SlotComp>
      	<icon name='icon' />
      	<div>默认的一些内容xxx</div>
    	</SlotComp>
    </>
  )
}

还可以使用 render Prop , 比较复杂的情况,比如需要向slot传值时,就可以使用 renderProp 

条件渲染的易错点

我犯过这种错, 现在知道原理了。 值得注意的是有一些 “falsy” 值,如数字 0,仍然会被 React 渲染。例如,以下代码并不会像你预期那样工作,因为当 props.messages 是空数组时,0 仍然会被渲染:

// 如果length为0, 页面会渲染 0.
<div>
  {props.messages.length && <MessageList messages={props.messages} />}
</div>

要解决这个问题,确保 && 之前的表达式总是布尔值

<div>
  {props.messages.length > 0 && <MessageList messages={props.messages} />}
</div>

性能优化

这里只说一个,虚拟长列表。 react-window 和 react-virtualized 

react-transition-group

类似vue的transition, 有一说一,vue的transition确实好用,增强了用户体验(也针对开发) 在react中,这样来写

import { Button } from 'antd'
import React, { useState } from 'react'
import { CSSTransition } from 'react-transition-group'
import './index.less'
import styles from './index.module.less'

const Ide = () => {
  const [box1, setBox1] = useState<boolean>(true)

  return (
    <>
      <CSSTransition in={box1} classNames='box1' unmountOnExit timeout={1000} onEnter={()=>{console.log('enter')}} onEntered={()=>{console.log('entered')}} onEntering={()=>{console.log('entering')}} mountOnEnter>
        <div className={styles.test}></div>
      </CSSTransition>

      <Button type='primary' onClick={() => setBox1(!box1)}>change</Button>
    </>
  )
}
export default Ide
.box1-enter {
  opacity: 0;
  transform: scale(0.5);
}

.box1-enter-active {
  opacity: 1;
  transition: all 1s;
  transform: scale(1);
}

.box1-exit {
  opacity: 1;
  transform: scale(1);
}

.box1-exit-active {
  transform: scale(0.5);
  transition: all 1s;
  opacity: 0;
}

关键参数介绍:

  • in: 控制transition显隐
  • unmountOnExit: 隐藏后是否卸载组件
  • mountOnEnter:    默认子组件会立即跟随transition组件一起挂载到dom上,如果想在第一次渲染时 in={true} 的情况下懒加载组件,就可以设置 mountOnEnter。如果指定了 mountOnEnter, 那么即使隐藏了,组件也不会卸载,除非设置了 unmountOnExit
  • timeout指定过渡时间,毫秒为单位。可以为每个过程单独设置过渡时间: {appear: 300, enter:400, exit: 500}。跟css中transition的过渡时间对应
  • 状态回调函数,onEnter之类的
  • appear 好像在 CSSTransition中没有用