zzzmj / duola-blog

哆啦的博客

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

React 一些高级特性和Hooks

zzzmj opened this issue · comments

commented

React新特性

目录

  1. Context和ContextType
  2. lazy和Suspense
  3. pureComponent 和 memo
  4. React Hooks

1. Context

React通过props自上到下传递数据,如果层级较深就会很麻烦

于是有了Context,它就像React中的全局变量,无需传递Props,组件树中就能进行数据传递

像react-redux中,传递store,就是利用了这个属性

1.1 使用Context

使用Context用到了生产者消费者模式
需要两个组件

  • <Provider> 生产者 (通常是父节点)
  • <Consumer> 消费者 (通常是子节点)

例子

import React from 'react';
import Main from './Context/Main';

// 通过该createContext静态方法创建一个Context对象
// 这个对象包含了Provider和Context
// 传值是默认值,也可以不传
const ThemeContext = React.createContext({
    background: 'red',
    color: 'black'
})

class App extends React.Component() {
    return (
        <ThemeContext.Provider value="dark">
            <Main/>
        </ThemeContext.Provider>
    );
}

class Main extends React.Component {
    render() {
        return (
            <ToolBar />
        )
    }    
}

class ToolBar extends React.Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (theme) => {
                        console.log('theme', theme)
                    }
                }
            </ThemeContext.Consumer>
        )
    }
}
export default App;

可以看见theme对象属性 跨了几个层级,传递到了ToolBar

看以看见 Provider 接收一个 value 属性,传递给Consumer组件使用

Context组件就像作用域链一样,Consumer使用 value 值的使用,会一层一层往上找,找到最近的Provider提供的value值

1.2 使用contextType

contextType其实就是语法糖,它约束了组件只能使用单一的context

在上面消费者写的形式不太优雅,通过contextType改写

class ToolBar extends React.Component {
    // 套路,后面的ThemeContext是你定义的context
    static contextType = ThemeContext
    render() {
        // 通过this.context可以拿到这个Provider提供的value值
        const theme = this.context
        return (
            <h1>
                {theme.background}
           </h1>
        )
    }
}

相关文章:
聊一聊我对 React Context 的理解以及应用

2. lazy和Suspense

lazy可以动态引入组件,让组件在被需要的时候动态引入

const Counter = React.lazy(() => import('./Counter'))

需要配合Suspense使用,因为组件在引入的时候需要加载,在模块还没加载出来的时候

可以通过Suspense来渲染加载状态, fallback中包裹这个加载状态

import React, {Suspense} from 'react';

<Suspense fallback={<div>Loading...</div>}>
    <Counter />
</Suspense>

异常捕获边界(Error boundaries)

错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI

如果由于网络原因,模块加载失败,组件树崩溃,那么错误组件就会渲染出备用UI

class ErrorBoundary extends React.Component {
    state = { 
        hasError: false 
    };

    static getDerivedStateFromError(error) {
        // 更新 state 使下一次渲染能够显示降级后的 UI
        return { hasError: true };
    }

    componentDidCatch(error, errorInfo) {
        // 你同样可以将错误日志上报给服务器
        logErrorToMyService(error, errorInfo);
    }

    render() {
        if (this.state.hasError) {
        // 你可以自定义降级后的 UI 并渲染
            return <h1>Something went wrong.</h1>;
        }

        return this.props.children; 
    }
}

3. pureComponent和memo

pureComponent就是帮忙实现了shouldComponentUpdate中浅层对象的对比

如果传入的props在浅层对比上没有变化,那么组件就不会被重新渲染,提高了性能。

memo是高阶组件,它和pureComponent的效果一样,它是针对于函数组件的

使用方法,用memo将函数组件包裹即可

const MyComponent = React.memo(function MyComponent(props) {
    
});

4. React Hooks

1. 为什么要用Hooks?

  1. 使用class的方式使得组件很难复用,一般只有两种方式

    • render props
    • 高阶组件

    这两者方式,特别是第二种,比较麻烦,而且不好理解

  2. 复杂组件变得难以理解
    组件越来越复杂的时候,会被逻辑和副作用充斥。

2. 简单的例子 和 userState

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量。
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

关键是userState这个函数

首先这个函数只接收一个参数, 就是初始state, useState(initState)

然后这个函数返回一对值,

  • 第一个值是:当前状态
  • 第二个值是:更新状态的函数 (类似于this.setState, 但是它不会合并新旧状态)

然后就能通过这个函数,完美实现了原来的this.state, this.setState模式

什么时候我会用 Hook?

如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。
现在你可以在现有的函数组件中使用 Hook。

3. 副作用 和 useEffect

在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。
我们统一把这些操作称为副作用

有两种常见的副作用

  • 不需要清除的:比如网络请求,记录日志,变更DOM等
  • 需要清除的:订阅,定时器等

3.1 不需要清除的副作用

在以往的class方式中,我们会在
componentDidMount、componentDidUpdate 和 componentWillUnmount中处理这些东西

现在使用 useEffect来代替它们。

function Counter() {
	const [count, setCount] = React.useState(0)

	React.useEffect(() => {
		console.log(count)
	})

	return (
		<div>
			Count: {count}
			<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
			<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
		</div>
	);
}

useEffect做了什么? 会在第一次渲染之后和每次更新后调用

为什么要在组件内部调用useEffect?将它放入组件内部可以直接使用state变量,这是利于到了闭包的特性

疑问1. 为什么componentDidMount 或 componentDidUpdate 会阻塞浏览器更新屏幕

与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。

3.2 需要清除的副作用

在class的方式中

// 在组件挂载后订阅
componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
        this.props.friend.id,
        this.handleStatusChange
    );
}

// 在组件销毁后取消订购
componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
        this.props.friend.id,
        this.handleStatusChange
    );
}

在hooks中,你可能认为要单独再使用一个函数来执行组件销毁后的操作

但设计仍然是在useEffect之中,如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它:

useEffect(() => {
    function handleStatusChange(status) {
        setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
});

3.3 深入理解useEffect

在上面的例子中,我们发现useEffect在每次状态更新 组件重新渲染后都会调用

可以通过useEffect第二个可选参数实现

useEffect(() => {
  console.log(count)
}, [count]); // 仅在 count 更改时更新

它会对更新之前的count和更新之后的count 做比对,如果发生了改变才去执行effect,否则就会跳过这个effect

注意,如果使用这种方式优化,确保数组中包含了这个useEffect中使用到的所有会随时间变化的变量

那如果我不想每次都重新渲染,比如说加载组件数据的网络请求

可能在原先的class组件上,我只想调用一次就够了,而不是每次状态更新都去重新发送请求

可以给第二个参数传一个空数组

// 只会执行一次了
useEffect(() => {
  // ...ajax请求
}, [])

其实原理是一样的,由于传了空数组,下一次更新的空数组等于第一次更新的空数组,所以就会跳过下一次更新

3.4 useContext

使用起来比class的方式方便很多

// 创建一个

const CounterContext = createContext(0)
function Counter() {
    return (
        <CounterContext.Provider value={count}>
		    <Bar></Bar>
        </CounterContext.Provider>
    )
}

// 而且使用多个context也是可以的
function Bar() {
	const count = useContext(CounterContext)
	return (
		<h1>{count}</h1>
	)
}

相关文章
官方文档