React && 前端部署

Posted by Bibooo on 08-01,2022

React && 前端部署

大多数 React 应用都会使用 Webpack,Rollup,Browserify 这类构建工具来打包文件。

打包是一个将文件引入并合并到一个单独文件的过程,最终形成一个 “bundle” 页面引入该 bundle,整个应用即可一次性加载。

像 Create React App ,Next.js,Gatsby,这些工具,你可以使用内置的 Webpack 配置构建你的应用。

我还是不喜欢脚手架,我更喜欢引入外链来写。

我目前在写一个常规项目,我打算最后打包一边学习一边使用 构建工具来打下包。

React.lazy

React.lazy 函数能让像处理常规组件渲染的动态引入。它接受一个函数,这个函数要 import 导入

我们应该是 Suspense 组件中渲染 lazy组件,Suspense 有个 fallback 属性它接受任何在组件加载中的 React 元素,所以我们可以做点 loading 加载什么的。路由也可以这样做。

Suspense甚至可以包括多个懒加载组件

当我们在Suspense 如果使用 tab 的话,如果 tab另外组件还没有准备好渲染其内容,react 为了保证用户,会展示 旧ui 这时候我们可以使用 startTransition 来解决。

Context

我记得之前我做了个分享连接的网站,数据都是通过 props 属性自下而上的,导致我的用户传输是极其繁琐的,整个组件似乎都在传递 user,像 ui主题 地区偏好 。

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的琢层传递 props。

使用 context,我们可以避免中间元素传递 props;

我们需要使用 React.createContext 创建一个元素 使用一个 Provider 将值传递下去 无论多深任何组件都能读取这个值。

const ColorTexy = React.createContext('green');
class Other extends React.Component{
    render(){
      return (<div>
          <ColorTexy.Provider value='red'>
            <Middle/>
          </ColorTexy.Provider>
      </div>)
    }
}
function Middle(){
  return (<Butto/>)
}
class Butto  extends React.Component{
  static contextType = ColorTexy;

  render(){
    return (<button style={{color:this.context}}>123</button>)
  }
}

const Root = ReactDOM.createRoot(document.getElementById('root'));
Root.render(<Other/>)

我说真的 奇奇怪怪的, static contextType = ColorTexy; ?这是把 创建的对象绑定在上下文了吗?context哪里来的?于是我怀着好奇心打印了一下

{$$typeof: Symbol(react.context), _currentValue: 'green', _currentValue2: 'green', _threadCount: 0, Provider: {…}, …}
$$typeof: Symbol(react.context)
Consumer: {$$typeof: Symbol(react.context), _context: {…}, …}
Provider: {$$typeof: Symbol(react.provider), _context: {…}}
_currentRenderer: {}
_currentRenderer2: null
_currentValue: "green"
_currentValue2: "green"
_defaultValue: null
_globalName: null
_threadCount: 0
[[Prototype]]: Object

我们看到 contextType 最新的值 green 已经更新了

Butto {props: undefined, context: undefined, refs: {…}, updater: {…}}
context: "red"
props: {}
refs: {}
state: null
updater: {isMounted: ƒ, enqueueSetState: ƒ, enqueueReplaceState: ƒ, enqueueForceUpdate: ƒ}
_reactInternalInstance: {_processChildContext: ƒ}
_reactInternals: FiberNode {tag: 1, key: null, stateNode: Butto, elementType: ƒ, type: ƒ, …}
isMounted: (…)
replaceState: (…)
[[Prototype]]: Component

Butto 的组件 context 已经更新了,是的这确实很奇怪 context 更新依赖究竟是怎么变化的?

provider 就像你使用任何其他 react 组件一样,它接受一个 value 道具,这是你希望任何 children都能获取数据

我们使用 react.createContext 创建语言环境对象

const ColorTexy = React.createContext('green');
class Other extends React.Component{
  constructor(){
    super()
    console.log(this)
  }
    render(){
      return (<div>
          <ColorTexy.Provider value='red'>
            <Middle/>
          </ColorTexy.Provider>
      </div>)
    }
}
function Middle(){
  return (<Butto/>)
}
class Butto  extends React.Component{
  
  render(){
    return (<ColorTexy.Consumer>
    {value =>{
      return <button style={{color:value}}>123 {console.log(this.context)}</button>
    }}
    </ColorTexy.Consumer>)
  }
}

我们可以在类.contextType = 创建上下文的对象

或者使用 static 定义类的静态方法,都可以把 value传递给上下文

也可以使用 Consumer函数,它接收当前 context 值,并返回 react 节点,如果没有对应的 provider 提供的 value值,就是创建上下文对象的默认值。那么我们使用 static 和 类的 contextType 我们没有对应的 provider 返回什么了? 也是创建上下文对象的默认值。

Context.displayName 我们可以使用 displayName 命名在 DevTools 中

React.createContext 方法非常灵活,我们可以传递一个函数,来更新 context

多个 Context

我们可以使用 多个consumers 组件,嵌套都可以非常灵活。

context 本质上是 value 属性值的浅比较,我们总是将 value 状态提升到父节点的 state 里。防止重渲染,当每一次 Provider 重渲染时,由于 value 属性总是被赋值为新的对象

错误边界

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
  
  componentDidCatch(error, errorInfo) {
    console.log(error,errorInfo);
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
  }
  
  render() {
    if (this.state.errorInfo) {
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    return this.props.children;
  }  
}

class BuggyCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  }
  
  render() {
    if (this.state.counter === 5) {
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  }
}

function App() {
  return (
    <div>
      <hr />
      <ErrorBoundary>
        <BuggyCounter />
        <BuggyCounter />
      </ErrorBoundary>
      <hr />
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

只有 class 组件才可以在成为错误边界组件。大多数情况下你只需要声明一次错误边界组件,并在整个应用中使用它

需要注意的是 错误边界只能捕获子组件的错误,它无法捕获其自身的错误

Facebook Messenger 将侧边栏、信息面板、聊天记录以及信息输入框包装在单独的错误边界中。如果其中的某些 UI 组件崩溃,其余部分仍然能够交互。

注意

错误边界无法捕获以下场景中产生的错误:

  • 事件处理(了解更多
  • 异步代码(例如 setTimeoutrequestAnimationFrame 回调函数)
  • 服务端渲染
  • 它自身抛出来的错误(并非它的子组件)

refs转发

function handleClick(){
   console.log(ref.current.innerText)
}
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton"  onClick={handleClick}>
    {props.children} 
  </button>
));
const ref = React.createRef();

class App extends React.Component{
  constructor(){
    super()
  }
  render(){
    return(
       <div><FancyButton ref={ref}>Click me!</FancyButton></div>
    )
}
}

我们点击打印可以清楚地看到可以获取 DOM 属性,那为什么我们不能 prop 直接传递 ref 呢?

 FancyButton: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop.

直接提示报错了叫我们使用 forwarfRef 。

转发 refs

可以获取 组件内部 DOM,只有在 React.forwadRef定义组件有效,常规函数和class 不接收 ref 参数,props 中也不存在 ref,props中也不存在ref。

import React from "react";

function logProps(WrappedComponent) {
    class LogProps extends React.Component {
      componentDidUpdate(prevProps) {
        console.log('old props:', prevProps);
        console.log('new props:', this.props);
      }
  
      render() {
        console.log(this.props)
        return <WrappedComponent {...this.props} />;
      }
    }
  
    return LogProps;
  }
  class FancyButton extends React.Component {
    constructor(props){
        super(props)
    }
    focus() {
      // ...
    }
     render(){
        return (<div onClick={this.props.handleClick}>hello</div>)
     }
    // ...
  }

   let  Fancy = logProps(FancyButton);
  export default Fancy;
  
  
  <Fancy label="Click Me"
  handleClick={this.handleClick}
  ref={ref}/>
  

ref 不会透传下去,因为 ref 不是 prop属性。如果你对 Hoc 添加 ref 该 ref 会引用最外层的容器组件。

高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧,Hoc 本身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

高级函数是参数为组件,返回值新组件的函数

组件是将 props 转化为 ui,高阶组件是将组件转化成另外一个函数

为什么使用它?

我们在前后端分离请求数据做业务,总是 在挂载时绑定状态,当数据变化时 更新状态 setState,卸载时,删除状态。

在大型应用中,这种 绑定状态,调用 setState 一次又一次的发生,抽象,高阶组件的出现,让我们可以在一个地方定义这个逻辑,并在许多组件共享它。

感觉很牛逼,我接触了 vue angular 我都没有发现这种功能,我们总是在请求,更新

与第三方协同

React 可以被用于任何 web 项目中,可以嵌入其他应用,其他应用可以嵌入 react 中。

React 不会理会 React 自身之外的 DOM 操作,它根据内部虚拟 DOM 来决定是否需要更新,而且如果同一个 DOM 节点被另一个库操作了, React 会觉得困惑而且没有办法恢复

避免冲突的最简单方式就是防止 React 更新。你可以渲染无需更新的 React 元素,比如一个空的 div

深入JSX

jsx仅仅只是 React.createElement(component,props,…children)函数的语法糖。

React 必须在作用域里

我们在使用 jsx 里必须导入 react库,因为 jsx 会编译为 React.createElement 调用形式。

不能使用表达式作为 React 元素类型,需要将类型赋值给 大写字母开头的 变量。

函数可以作为子元素

  • 布尔,null,undefined 将会忽略