PengJack
8/2/2019 - 6:12 AM

深入理解 React

深入理解 React

// 属性代理, 抽离 state, 组件 ref
// 链接: https://juejin.im/post/5c72b97de51d4545c66f75d5

import React, { Component } from 'react';
import { Input, message } from 'antd';

function withOnChange(WrappedComponent) {
  return class extends Component {
    sonRef = {};

    state = {
      name: '',
      sex: 'male',
    };

    onChange = e => {
      this.setState({ name: e.target.value });
    };

    getRef = instance => {
      this.sonRef = instance;
    };

    componentDidMount() {
      this.sonRef.sayHello();
    }

    render() {
      const newProps = {
        value: this.state.name,
        onChange: this.onChange,
        son: 'chenchen',
      };

      return (
        <WrappedComponent ref={this.getRef} {...this.props} {...newProps} />
      );
    }
  };
}

class NameInput extends Component {
  sayHello = () => {
    message.success('hello');
  };

  render() {
    return <Input name="name" {...this.props} style={{ width: 200 }} />;
  }
}

const HighOrderComp = withOnChange(NameInput);
export default HighOrderComp;



// 注意事项

// 1 不要在 render 方法中使用 HOC

// React 的 diff 算法(称为协调)使用组件标识来确定它是应该更新现有子树还是将其丢弃并挂载新子树。 
// 如果从 render 返回的组件与前一个渲染中的组件相同(===),则 React 通过将子树与新子树进行区分来递归更新子树。 
// 如果它们不相等,则完全卸载前一个子树。

render() {
  // 每次调用 render 函数都会创建一个新的 EnhancedComponent
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!
  return <EnhancedComponent />;
}


// 2 务必复制静态方法

// 当你将 HOC 应用于组件时,原始组件将使用容器组件进行包装。这意味着新组件没有原始组件的任何静态方法。


// 定义静态函数
WrappedComponent.staticMethod = function() {/*...*/}
// 现在使用 HOC
const EnhancedComponent = enhance(WrappedComponent);

// 增强组件没有 staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true


// 为了解决这个问题,你可以在返回之前把这些方法拷贝到容器组件上:

function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  // 必须准确知道应该拷贝哪些方法 :(
  Enhance.staticMethod = WrappedComponent.staticMethod;
  return Enhance;
}

















/*反向继承其实就是 一个函数接受一个 WrappedComponent 组件作为参数传入,
  并返回一个继承了该传入 WrappedComponent 组件的类,且在该类的 render() 方法中返回 super.render() 方法。
*/

import React, { Component } from 'react';

function withLogger(WrappedComponent) {
  // return 的是 WrappedComponent 的一个子类
  return class extends WrappedComponent {
    render() {
      // 这里的 this 是指 WrappedComponent 的子类的实例,继承了 WrappedComponent 所有的实例和原型属性和方法
      console.log(this.__proto__.__proto__ === WrappedComponent.prototype);
      console.log(this);
      this.sayHello(); // 调用父类的原型方法
      this.sayGoodbye(); // 调用父类的实例方法

      const newProps = {
        father: 'pengguoxi',
        age: 66,
      };

      return (
        <div>
          <h2>Debugger Component Logging...</h2>
          <p>state:</p>
          <pre>{JSON.stringify(this.state, null, 4)}</pre>
          <p>props:</p>
          <pre>{JSON.stringify(this.props, null, 4)}</pre>
          {super.render()}
        </div>
      );
    }
  };
}

class Father extends Component {
  state = {
    age: 66,
    name: 'PengGuoxi',
  };

  // 定义在 Son.prototype 原型上的方法
  sayHello() {
    console.log('hello');
  }

  // 定义在 Son 实例上的方法
  sayGoodbye = () => {
    console.log('goodbye');
  };

  render() {
    return (
      <div className="father">
        <h2>this is Father comp</h2>
      </div>
    );
  }
}
const father1 = new Father();
console.log('father1: ', father1);

const Son = withLogger(Father);
export default Son;
// 一个组件消费多个 context 的情况


import React, { Component } from 'react';

// Theme context,默认的 theme 是 “light” 值
const ThemeContext = React.createContext('light');

// 用户登录 context
const UserContext = React.createContext({
  name: 'Guest',
});

class MyClass extends React.Component {
  render() {
    return (
      <div>
        <ThemeContext.Consumer>
          {theme => (
            <UserContext.Consumer>
              {user => (
                <h2>
                  theme: {theme} user: {user}{' '}
                </h2>
              )}
            </UserContext.Consumer>
          )}
        </ThemeContext.Consumer>
      </div>
    );
  }
}

class DeviceDev extends Component {
  render() {
    return (
      <div>
        <ThemeContext.Provider value="dark">
          <UserContext.Provider value="jack">
            <MyClass />
          </UserContext.Provider>
        </ThemeContext.Provider>
      </div>
    );
  }
}

export default DeviceDev;
// 来自 react 官方文档示例  https://codepen.io/gaearon/pen/wqvxGa?editors=0010

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
  
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
    // You can also log error messages to an error reporting service here
  }
  
  render() {
    if (this.state.errorInfo) {
      // Error path
      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>
      );
    }
    // Normally, just render children
    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) {
      // Simulate a JS error
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  }
}

function App() {
  return (
    <div>
      <p>
        <b>
          This is an example of error boundaries in React 16.
          <br /><br />
          Click on the numbers to increase the counters.
          <br />
          The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
        </b>
      </p>
      <hr />
      <ErrorBoundary>
        <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
        <BuggyCounter />
        <BuggyCounter />
      </ErrorBoundary>
      <hr />
      <p>These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.</p>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
    </div>
  );
}



ReactDOM.render(
  <App />,
  document.getElementById('root')
);
// React.forwardRef() 函数接收一个函数作为参数,注意必须是函数,不能是类组件,这个函数接收 props 和 ref 两个参数
// 可以把这个函数参数当做一个函数组件来看待,只是和普通的函数组件不一样,除了 props 外,还接受第二个参数 ref
// 普通的函数组件和类组件的 props 对象里面是没有 ref 属性的
// 可以把 React.forwardRef 看做是一个高阶组件,接受一个特殊的函数组件,然后返回一个新的组件
// 作用在这个新的组件上的 ref 属性会被转发到内部的组件或者 dom 元素上


const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

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


// 在高阶组件里面转发 refs

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // 注意 React.forwardRef 回调的第二个参数 “ref”。
  // 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
  // 然后它就可以被挂载到被 LogPros 包裹的子组件上。
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}



// 如果你命名了渲染函数,DevTools 也将包含其名称(例如 “ForwardRef(myFunction)”):

const WrappedComponent = React.forwardRef(
  function myFunction(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }
);


// 你甚至可以设置函数的 displayName 属性来包含被包裹组件的名称:

function logProps(Component) {
  class LogProps extends React.Component {
    // ...
  }

  function forwardRef(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }

  // 在 DevTools 中为该组件提供一个更有用的显示名。
  // 例如 “ForwardRef(logProps(MyComponent))”
  const name = Component.displayName || Component.name;
  forwardRef.displayName = `logProps(${name})`;

  return React.forwardRef(forwardRef);
}