笔记:React 17.0 类组件

本文详细介绍了如何创建React项目,包括配置Less和CSSModule,设置代理,以及JSX、React.Component、State、Refs、生命周期、组件通信等多种核心概念。还探讨了React.PureComponent、React.memo优化性能的方法,以及高阶组件、Fragments、Portals等高级特性。此外,还涵盖了错误边界、Suspense和Transition等错误处理和动画过渡技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

创建React新项目

npm install -g create-react-app
create-react-app "项目名" --template typescript

配置Less和CSS Module

npm i -D less less-loader@5 
npm i -D customize-cra@1.0.0-alpha.0 react-app-rewired

package.json中的script修改为以下代码。

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test --env=jsdom",
  "eject": "react-scripts eject"
},

package.json同目录添加config-overrides.js文件。

const { override, addLessLoader, adjustStyleLoaders } = require('customize-cra');

module.exports = override(
  addLessLoader({
    strictMath: true,
    noIeCompat: true,
    loader: "css-loader",
    options: {
      modules: {
        localIdentName: "[name]__[local]___[hash:base64:5]",
      },
      sourceMap: true
    }
  }),
  adjustStyleLoaders(({ use: [, , postcss] }) => {
    const postcssOptions = postcss.options;
    postcss.options = { postcssOptions };
  })
)

配置代理

package.json同目录添加setupProxy.js文件。

const { createProxyMiddleware } = require('http-proxy-middleware')

module.exports = function (app) {
  app.use(
    createProxyMiddleware('/api', {
      target: 'http://localhost:5000',    // 代理服务器转发给服务端的地址
      changeOrigin: true,                 // 控制服务器收到请求头中Host字段的值
      pathRewrite: { '^/api': '' },       // 重写路径
    })
  )
}

JSX

①JSX语法经过Babel的转译,自动调用React.createElement()方法,创建虚拟DOM元素。
②只能有一个根标签,JS表达式用{}包裹。
③组件标签首字母大写,组件标签可以使用命名空间。HTML元素标签首字母小写。
④标签多个属性的配置对象可以批量传入。
⑤React将所有要展示到网页的字符进行转译,防止XSS攻击。如果需要避免React转译,可以使用dangerouslySetInnerHTML属性。

<Comp.Btn data-custom="" onClick={() => {}} {...props}>
  <div className={} dangerouslySetInnerHTML={{_html: ""}}>内容</div>
</Comp.Btn>

React.Component

①只有当组件的stateprops发生改变时,组件才会更新。如果父组件更新,其子组件也会更新。
React.Component类声明了propscontextrefs等,并在原型上定义了setState()forceUpdate(),以及生命周期等方法。
③表单等需要用state动态储存内部状态,用onChange()事件双向绑定数据的组件为受控组件。反之为非受控组件。

State

setState()在批量更新模式时,用队列异步合并一个生命周期内所有的状态更新,并仅更新组件一次。
setState()在非批量更新模式时,效果类似同步调用,例如在定时器中。V18新特性中,setState()全局开启批量更新模式。
setState()修改状态有两种方式:
1、参数1是状态数据,对象是合并的操作。参数2是状态更新后的回调函数。
2、参数1是函数,传入最新的stateprops,返回修改后的状态数据,能防止多次修改一个状态的过滤。参数2是状态更新后的回调函数。
④初始化stateconstructor()中,当不需要bind()事件函数时,可以简写为实例属性。

class CC extends Component {
  // 方式一
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  // 方式二
  state = { count: 0 }
  // 修改state
  add = () => {
    this.setState({ count: this.state.count + 1 }, () => {});
    this.setState((state, props) => ({ count: this.state.count + 1 }), () => {});
  }

  render() {
    return <div></div>;
  }
}
export default CC;

Props

①在React中,数据通过props单向流动。props只读不可变,默认值可以通过静态变量defaultProps定义。
props中内置一个children属性,表示子组件数组,可以用React.Children中的方法对其操作,包括map()forEach()count()等。

class CC extends Component {
  // 参数默认值
  static defaultProps = {};
  
  render() {
    const { children } = this.props;
    return <div>{children}</div>;
  }
}
export default CC;

③父组件向子组件传递组件,可以实现类似插槽的效果,即Render Props

class CC extends Component {
  renderComp = (args) => {
    return <div {...args} />
  }
  
  render() {
    return <Comp render={this.renderComp} />;
  }
}
export default CC;

Ref

①尽量少用Ref去模拟表单等受控组件。
②常用的方式有两种:回调形式和React.createRef()
③将子组件用React.forwardRef()包裹,则父组件能够通过Ref获取到子组件的真实DOM。

class CC extends Component {
  Ref = null;
  // 回调形式
  getRef = (element) => {
    this.Ref = element;
  }
  // React.createRef()
  Ref = React.createRef();
  render () {
    return (
      // 回调形式
      <div ref={this.getRef}>{this.Ref}</div>
      // React.createRef()
      <div ref={this.Ref}>{this.Ref.current}</div>
      // 转发ref
      <div ref={this.props.CompRef} />
    )
  }
}
export default React.forwardRef((props, ref) => <CC {...props} CompRef={ref} />);

合成事件

①类组件中事件函数的this需要绑定,否则在执行事件函数时this指向函数调用的上下文(Window),this指向丢失。
有两种解决方式:
1、在constructor()bind()当前实例。
2、使用箭头函数。
②React合成事件模拟原生捕获和冒泡阶段,从而达到不同浏览器兼容的目的。合成事件函数被统一绑定到root根元素,用事件委托的方式触发合成事件。
③使用原生DOM事件一定要在组件卸载时手动移除。合成事件在组件卸载时自动移除。
④React合成事件没有实现全部DOM事件,例如Window的resize事件。

class CC extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    // 方法一
    this.add = this.add.bind(this);
  }
  // 方法一
  add() {
    this.setState({ count: this.state.count + 1 });
  }
  // 方法二
  add = () => {
    this.setState({ count: this.state.count + 1 });
  }
  
  render() {
    return (
      <div onClick={this.add} onClickCapture={}>{this.state.count}</div>
    );
  }
}
export default CC;

生命周期

在这里插入图片描述
constructor()在组件挂载之前调用,函数中通常只给state赋初值或绑定事件函数,不允许在其中调用setState()方法。
componentWillUnmount()在组件挂在完毕时调用,componentWillUnmount()在组件将要卸载时调用。
shouldComponentUpdate()决定组件的更新。可以浅比较propsstate控制组件的更新,返回false能够阻止组件更新。

class CC extends Component {
  constructor(props) {
    super(props);
  }
  componentDidMount() {}
  componentWillUnmount() {}
  componentDidUpdate(prevProps, prevState, snapshot) {}
  shouldComponentUpdate(nextProps, nextState) {}
  render() {
    return <div></div>;
  }
}
export default CC;

React.PureComponent

React.PureComponent浅层对比propstate的方式来实现了shouldComponentUpdate()函数,决定是否更新组件。

React.memo

React.memo是高阶组件,若包裹的组件的props浅层比较后未发生改变,则不会更新。
②第二个参数可以自定义比较props

React.memo(Component, (prevProps, nextProps) => {});

组件通信

方式一:props (父组件 → 子组件)

①父组件将变量或函数传递给子组件,子组件用props接受。
②此方式可以级联,实现祖先组件→后代组件。

方式二:自定义事件 (子组件 → 父组件)

①父组件向子组件传递预定义的函数,子组件调用该函数即可将数据传递给父组件。
②子组件通过Ref转发,将自身DOM传递给父组件。

方式三:Context (祖先组件 → 后代组件)

React.createContext创建一个context对象,且可以传入默认值,在子组件未匹配到提供context的祖先组件时,展示默认值。
②当祖先组件提供的数据变化时,使用到数据的子组件就会更新。
③在提供context的祖先组件中,用<context.Provider>包裹,用value属性向子组件提供全局数据。

export const Store = React.createContext(defaultValue);
class CC extends Component {
  render() {
  	const ctx = {key: value};
    return (
    	<Store.Provider value={ctx}>
    		<Child />
    	</Store.Provider>
    );
  }
}
export default CC;

④子组件可以通过<context.Consumer>包裹并传递函数,来获取祖先组件提供的全局变量。

class Child extends Component {
  render() {
    return (
    	<Store.Consumer>
    		{cxt => <div>{ctx.key}</div>}
    	</Store.Consumer>
    );
  }
}
export default CC;

⑤子组件的静态属性contextType赋值为context也能通过this.context获取到祖先组件提供的全局数据。

class Child extends Component {
  static contextType = Store;
  render() {
    return <div>{this.context.key}</div>;
  }
}
export default CC;

方式四:消息订阅与发布 (任意组件通信)

①借助pubsub-js第三方库提供的消息订阅与发布,或是EventEmitter来全局保存事件,广播处理事件进行组件通信。

方式五:React状态管理库 (任意组件通信)

①详见另一篇博客:React 状态管理库

高阶组件

①高阶组件接受组件为输入,输出一个新的组件。属性代理反向继承都可以实现高阶组件。
②再次用函数包裹高阶组件能够传入额外的参数,类似函数柯里化。
属性代理: 能增强props,共享context

const HOC = (WrappedComp) =>
  class extends Component {
    render() { 
      const otherProps = {};
      return <WrappedComp {...this.props} {...otherProps} />;
    }
  };
export default HOC(WrappedComp);
// 再次包裹可以传入额外参数
const WrappedHOC = (...args) => {
  console.log(args)
  return HOC;
};
export default WrappedHOC(args)(WrappedComp);

反向继承: 能获取原组件实例的属性,劫持渲染,劫持生命周期。

const HOC = (WrappedComp) =>
  class extends WrappedComp {
    render() {
      return super.render();
    }
  };
export default HOC(WrappedComp);

Fragments

①空标签用于作为根节点,打包多个组件或标签,可简写。
key属性能够添加唯一标识,简写时不能添加key属性。

<React.Fragment key={}></React.Fragment>
<></> 										// 简写

Portals

①将子组件传送并渲染到父组件以外的真实DOM节点。
②虽然子组件被Protals传送到任意地方,但context和合成事件冒泡仍与VDOM中的父组件关联。

ReactDOM.createPortal(this.props.children, targetDOM);

Error boundaries

①错误边界只可以是一个类函数,可以捕获子组件内部抛出的错误,除了事件处理函数、异步代码、服务端渲染、错误边界组件它自身抛出来的错误。
getDerivedStateFromError()渲染备用兜底组件 ,componentDidCatch()记录错误信息。

class ErrorBoundary extends Component {
  state = { error: null };
  // 更新 state,下次渲染可以展示错误相关的 UI
  static getDerivedStateFromError(error) {
    return { error: error };
  }
  // 错误上报
  componentDidCatch(error, info) {}
  render() {
    // 渲染出错时的 UI
    if (this.state.error) return <p></p>;
    return this.props.children;
  }
}

Suspense

Transitions

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我才是真的李成贤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值