笔记:React 17.0 类组件
创建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
①只有当组件的state
和props
发生改变时,组件才会更新。如果父组件更新,其子组件也会更新。
②React.Component
类声明了props
、context
、refs
等,并在原型上定义了setState()
和forceUpdate()
,以及生命周期等方法。
③表单等需要用state
动态储存内部状态,用onChange()
事件双向绑定数据的组件为受控组件。反之为非受控组件。
State
①setState()
在批量更新模式时,用队列异步合并一个生命周期内所有的状态更新,并仅更新组件一次。
②setState()
在非批量更新模式时,效果类似同步调用,例如在定时器中。V18新特性中,setState()
全局开启批量更新模式。
③setState()
修改状态有两种方式:
1、参数1是状态数据,对象是合并的操作。参数2是状态更新后的回调函数。
2、参数1是函数,传入最新的state
与props
,返回修改后的状态数据,能防止多次修改一个状态的过滤。参数2是状态更新后的回调函数。
④初始化state
在constructor()
中,当不需要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()
决定组件的更新。可以浅比较props
和state
控制组件的更新,返回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
浅层对比prop
和state
的方式来实现了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;
}
}