前言
本篇文章记录的是上手react并做好一个React项目的大概知识点,比如jsx本质、生命周期、组件嵌套、父子通信、组件通信、插槽机制、跨组件通信、setState、react性能优化、ref、受控和非受控组件、高阶组件、React中使用样式。
jsx 本质
jsx 本质就是React.createElement + vdom,最终返回一个初始化好的react的实例对象。就像vue中的模板语法,其实也是h函数加vdom,最终返回一个初始化好的vue的实例对象一样。
const dom = React.createElement('div',{ id: 'test-dom'}, 'AAAA')
console.log('dom', dom)
const dom2 = <div id='test-dom'>AAAA</div>
console.log('dom2', dom2)
生命周期
getDerivedStateFromProps 允许你在render之前改一下state,返回null就不更新state。
shouldComponentUpdate 是否需要渲染
render 渲染
componentDidMount 渲染之后
componentDidUpdate 更新之后
componentWillUnmount 即将卸载
getSnapshotBeforeUpdate 可以获得上一次真实渲染的快照。
组件嵌套
函数组件和类组件可以嵌套
import React, { Component } from 'react';
// Header
function Header() {
return <h2>Header组件</h2>
}
// Main
function Banner() {
return <h3>Banner组件</h3>
}
function ProductList() {
return (
<ul>
<li>列表1</li>
<li>列表2</li>
</ul>
)
}
function Main() {
return (
<div>
<Banner/>
<ProductList/>
</div>
)
}
// Footer
function Footer() {
return <h2>我是Footer组件</h2>
}
export default class App extends Component {
render() {
return (
<div>
<Header/>
<Main/>
<Footer/>
</div>
)
}
}
父传子通信
分为类组件传值,和函数组件传值,还有属性的验证。
类组件
import React, { Component } from 'react';
class ChildCpn extends Component {
constructor() {
super();
}
componentWillMount() {
}
componentDidMount() {
console.log(this.props, "componentDidMount");
}
render() {
// console.log(this.props, "render");
const {name, age, height} = this.props;
return (
<h2>子组件展示数据: {name + " " + age + " " + height}</h2>
)
}
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn name="m2" age="26" height="1.88" />
<ChildCpn name="diao" age="27" height="1.98" />
</div>
)
}
}
函数组件
import React, { Component } from 'react';
function ChildCpn(props) {
const { name, age, height } = props;
return (
<h2>{name + age + height}</h2>
)
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn name="m2" age="26" height="1.88" />
<ChildCpn name="diao" age="27" height="1.98" />
</div>
)
}
}
属性验证
import React, { Component } from 'react';
import PropTypes from 'prop-types';
function ChildCpn(props) {
const { name, age, height } = props;
console.log(name, age, height);
const { names } = props;
return (
<div>
<h2>{name + age + height}</h2>
<ul>
{
names.map((item, index) => {
return <li>{item}</li>
})
}
</ul>
</div>
)
}
class ChildCpn2 extends Component {
// es6中的class fields写法
static propTypes = {
}
static defaultProps = {
}
}
ChildCpn.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
height: PropTypes.number,
names: PropTypes.array
}
// 默认值
ChildCpn.defaultProps = {
name: "m2",
age: 30,
height: 1.98,
names: ["mmm", "2222"]
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn name="m2" age={18} height={1.88} names={["m2", "mm22"]}/>
<ChildCpn name="diao" age={40} height={1.98} names={["aiyou", "diao"]}/>
<ChildCpn/>
</div>
)
}
}
子父通信
子父通信一般是函数传递。
import React, { Component } from 'react';
class CounterButton extends Component {
render() {
const {onClick} = this.props; // 父组件传递过来的函数
return <button onClick={onClick}>+1</button>
}
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+</button>
{/* 给子组件传递一个函数 */}
<CounterButton onClick={e => this.increment()} name="diao"/>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
组件通信
这种通过函数传递数据的方式,在vue中也可以,都是通过自定义事件来完成,只不过vue中可以使用watch或者computed来监听,而react中是通过getDerivedStateFromProps来进行监听。
TabControl
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class TabControl extends Component {
constructor(props) {
super(props);
this.state = {
currentIndex: 0
}
}
render() {
const { titles } = this.props;
const {currentIndex} = this.state;
return (
<div className="tab-control">
{
titles.map((item, index) => {
return (
<div key={item}
className={"tab-item " + (index === currentIndex ? "active": "")}
onClick={e => this.itemClick(index)}>
<span>{item}</span>
</div>
)
})
}
</div>
)
}
itemClick(index) {
this.setState({
currentIndex: index
})
const {itemClick} = this.props;
itemClick(index);
}
}
TabControl.propTypes = {
titles: PropTypes.array.isRequired
}
App
import React, { Component } from 'react';
import TabControl from './TabControl';
export default class App extends Component {
constructor(props) {
super(props);
this.titles = ['新款', '精选', '流行'];
this.state = {
currentTitle: "新款",
currentIndex: 0
}
}
render() {
const {currentTitle} = this.state;
return (
<div>
<TabControl itemClick={index => this.itemClick(index)} titles={this.titles} />
<h2>{currentTitle}</h2>
</div>
)
}
itemClick(index) {
this.setState({
currentTitle: this.titles[index]
})
}
}
插槽机制
众所周知vue中有插槽机制,但是react中也能够实现插槽,无论是普通插槽还是具名插槽都可以。
App
import React, { Component } from 'react';
import NavBar from './NavBar';
import NavBar2 from './NavBar2';
export default class App extends Component {
render() {
const leftJsx = <span>aaa</span>;
return (
<div>
{/* 普通插槽 */}
<NavBar name="" title="" className="">
<span>aaa</span>
<strong>bbb</strong>
<a href="/#">ccc</a>
</NavBar>
{/* 具名插槽 */}
<NavBar2 leftSlot={leftJsx}
centerSlot={<strong>bbb</strong>}
rightSlot={<a href="/#">ccc</a>} />
</div>
)
}
}
NavBar
import React, { Component } from 'react'
export default class NavBar extends Component {
render() {
// this.props.children;
return (
<div className="nav-item nav-bar">
<div className="nav-left">
{/* 通过children来取出父组件传递的内容,这里是通过索引取出其中索引为1的内容,直接整个children也行 */}
{this.props.children[0]}
</div>
<div className="nav-item nav-center">
{this.props.children[1]}
</div>
<div className="nav-item nav-right">
{this.props.children[2]}
</div>
</div>
)
}
}
NavBar2
import React, { Component } from 'react'
export default class NavBar2 extends Component {
render() {
const {leftSlot, centerSlot, rightSlot} = this.props;
return (
<div className="nav-item nav-bar">
<div className="nav-left">
{leftSlot}
</div>
<div className="nav-item nav-center">
{centerSlot}
</div>
<div className="nav-item nav-right">
{rightSlot}
</div>
</div>
)
}
}
跨组件通信
props 通信
import React, { Component } from 'react';
function ProfileHeader(props) {
return (
<div>
<h2>昵称: {props.nickname}</h2>
<h2>等级: {props.level}</h2>
</div>
)
}
function Profile(props) {
return (
<div>
{/* <ProfileHeader nickname={ props.nickname } level={ props.level }/> */}
<ProfileHeader {...props}/>
<ul>
<li>设置1</li>
<li>设置2</li>
</ul>
</div>
)
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
nickname: "kobe",
level: 99
}
}
render() {
// const {nickname, level} = this.state;
return (
<div>
{/* 通过... 可以 以平铺的方式给组件赋值 */}
<Profile {...this.state}/>
</div>
)
}
}
context 通信
创建context,设置contextType。不使用UserContext.Provider就是传默认值,使用UserContext.Provider可以传递自定义值。
import React, { Component } from 'react';
// 创建Context对象
const UserContext = React.createContext({
nickname: "m2",
level: -1
})
class ProfileHeader extends Component {
render() {
console.log(this.context);
// jsx ->
return (
<div>
<h2>昵称: {this.context.nickname}</h2>
<h2>等级: {this.context.level}</h2>
</div>
)
}
}
ProfileHeader.contextType = UserContext;
function Profile(props) {
return (
<div>
<ProfileHeader />
<ul>
<li>设置1</li>
<li>设置2</li>
</ul>
</div>
)
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
nickname: "diao",
level: 99
}
}
render() {
return (
<div>
{/* 给设置了contextType的注入state */}
<UserContext.Provider value={this.state}>
<Profile />
</UserContext.Provider>
<hr/>
{/* 没使用Context。Provider 就是默认值 */}
<Profile />
</div>
)
}
}
context 函数通信
创建context,通过UserContext.Consumer中包裹函数的方式使用传递过来的值。不使用UserContext.Provider就是传默认值,使用UserContext.Provider可以传递自定义值。
import React, { Component } from 'react';
// 创建Context对象
const UserContext = React.createContext({
nickname: "m2",
level: -1
})
function ProfileHeader() {
return (
<UserContext.Consumer>
{
value => {
return (
<div>
<h2>昵称: {value.nickname}</h2>
<h2>等级: {value.level}</h2>
</div>
)
}
}
</UserContext.Consumer>
)
}
function Profile(props) {
return (
<div>
<ProfileHeader />
<ul>
<li>设置1</li>
<li>设置2</li>
</ul>
</div>
)
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
nickname: "diao",
level: 99
}
}
render() {
return (
<div>
<UserContext.Provider value={this.state}>
<Profile />
</UserContext.Provider>
<hr/>
<Profile />
</div>
)
}
}
多context 通信
和context 函数通信一样,但是可以多层嵌套Provider 和 Consumer来用。
import React, { Component } from 'react';
// 创建Context对象
const UserContext = React.createContext({
nickname: "aaaa",
level: -1
})
const ThemeContext = React.createContext({
color: "black"
})
function ProfileHeader() {
// jsx -> 嵌套的方式
return (
<UserContext.Consumer>
{
value => {
return (
<ThemeContext.Consumer>
{
theme => {
return (
<div>
<h2 style={{color: theme.color}}>用户昵称: {value.nickname}</h2>
<h2>用户等级: {value.level}</h2>
<h2>颜色: {theme.color}</h2>
</div>
)
}
}
</ThemeContext.Consumer>
)
}
}
</UserContext.Consumer>
)
}
function Profile(props) {
return (
<div>
<ProfileHeader />
<ul>
<li>设置1</li>
<li>设置2</li>
</ul>
</div>
)
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
nickname: "kobe",
level: 99
}
}
render() {
return (
<div>
<UserContext.Provider value={this.state}>
<ThemeContext.Provider value={{ color: "red" }}>
<Profile />
</ThemeContext.Provider>
</UserContext.Provider>
</div>
)
}
}
全局事件传递EventBus
其实就是一个自定义观察者,一端监听,一端触发这样子,就像是vue中的总线传值EventBus。
import React, { PureComponent } from 'react';
import { EventEmitter } from 'events';
// 事件总线: event bus
const eventBus = new EventEmitter();
class Home extends PureComponent {
componentDidMount() {
eventBus.addListener("sayHello", this.handleSayHelloListener);
}
componentWillUnmount() {
eventBus.removeListener("sayHello", this.handleSayHelloListener);
}
handleSayHelloListener(num, message) {
console.log(num, message);
}
render() {
return (
<div>
Home
</div>
)
}
}
class Profile extends PureComponent {
render() {
return (
<div>
Profile
<button onClick={e => this.emmitEvent()}>点击了profile按钮</button>
</div>
)
}
emmitEvent() {
eventBus.emit("sayHello", 123, "Hello Home");
}
}
export default class App extends PureComponent {
render() {
return (
<div>
<Home/>
<Profile/>
</div>
)
}
}
setState
说setStae 之前,先讲讲 state 不可变,不要去直接对this.state对象做更改。更不要更改之后再去调用setState方法。
正确的应该是拷贝一份新的state数据,然后在setState把新的数据传进去这样子。
异步setState
setState在在React的合成事件以及hook中是异步的,但在合成事件和hook中也有两种方式来获取更新后的state值。
第一种是在componentDidUpdate生命周期中获取,另一种是在setState函数的第二个回调中获取更新后的state值。
import React, { Component } from 'react'
function Home(props) {
// 我还没更新呢
return <h1>{props.message}</h1>
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "我还没更新呢"
}
}
render() {
return (
<div>
<h2>当前计数: {this.state.message}</h2>
<button onClick={e => this.changeText()}>改变文本</button>
<Home message={this.state.message}/>
</div>
)
}
componentDidUpdate() {
// 方式二: 获取异步更新的state
console.log(this.state.message);
}
changeText() {
// 由于setState是异步更新
// this.setState({
// message: "你好啊,李银河"
// })
// console.log(this.state.message); // 同步代码先执行,所以打印 我还没更新呢
// 方式一: 获取异步更新后的数据
// setState(更新的state, 回调函数)
this.setState({
message: "我更新了"
}, () => {
console.log(this.state.message);
})
}
}
同步setState
由于setState是模拟异步来维持一个任务队列,然后批量执行任务队列中的任务,但这只能够在React的合成事件及hook中来模拟,所以当你在原生的setTimout中或者原生的事件中使用setState,就会变成同步了。
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "Hello World"
}
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={e => this.changeText()}>改变文本</button>
<button id="btn">改变文本2</button>
</div>
)
}
componentDidMount() {
// 情况二:原生的事件中使用setState
document.getElementById("btn").addEventListener("click", (e) => {
this.setState({
message: "原生事件"
})
console.log(this.state.message);
})
// this.setState({
// message: "你好啊,李银河"
// })
// console.log(this.state.message);
}
changeText() {
// 情况一: 将setState放入到定时器中
setTimeout(() => {
Promise.resolve().then(() => {console.log('我是微任务,如果有setState是异步,我会先输出Hello World,但它不是,所以输出:', this.state.message)})
this.setState(() => ({
message: "定时器"
}))
console.log(this.state.message);
}, 0);
}
}
setState数据合并
setState是模拟异步来维持一个任务队列,然后批量执行任务队列中的任务。
他是怎么合并多个setState的呢?
答案是:Object.assign({}, this.state, {message: "m2 aiyoudiao"})
setState自身合并
多次SetState会被进行合并,所以会导致你多次加加都只加一次,如果你想在setState合并时进行累加,可以使用setState中的参数,可以拿到上一次setState的结果。
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
</div>
)
}
increment() {
// 1.setState本身被合并
// this.setState({
// counter: this.state.counter + 1
// });
// this.setState({
// counter: this.state.counter + 1
// });
// this.setState({
// counter: this.state.counter + 1
// });
// 2.setState合并时进行累加
this.setState((prevState, props) => {
return {
counter: prevState.counter + 1
}
});
this.setState((prevState, props) => {
return {
counter: prevState.counter + 1
}
});
this.setState((prevState, props) => {
return {
counter: prevState.counter + 1
}
});
}
}
React 性能优化
key的唯一
react中遍历的时候需要给元素添加key,这是用来提高性能的,因为有了key之后,可以做到精准替换,只替换key相同但有其它变化的节点。
如果没有key,或者key是遍历时的index,那么在diff对比时,元素的顺序不对就会导致整个列表全部替换。
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
movies: ["天罗地网", "大卫天龙"]
}
}
render() {
return (
<div>
<h2>电影列表</h2>
<ul>
{
this.state.movies.map((item, index) => {
// 通过观察浏览器开发人员工具中的element中的元素变化。
// return <li key={index}>{item}</li>
return <li key={item}>{item}</li>
})
}
</ul>
<button onClick={e => this.insertMovie()}>添加</button>
</div>
)
}
insertMovie() {
// this.setState({
// movies: [...this.state.movies, "大话西游"]
// })
// 替换一下顺序
this.setState({
movies: ["斗罗大陆", ...this.state.movies]
})
}
}
组件嵌套的render调用
调用的过程是深度优先遍历的,从上到下,从外到内。
只要state一发生变化,就会从头到尾的去执行整个页面所有组件的render函数。
虽然页面几乎无感,但是去递归执行那么多组件的钩子,的确是耗性能。
shouldComponentUpdate
shouldComponentUpdate:组件是否需要更新
当这个react生命周期钩子中返回false时,则不会执行后面的render操作,这样性能就得到了提升。
它是通过你在钩子函数内部写一些判断,判断当前组件的state是否发生变化,如若没有变化,则不需要更新组件了。
import React, {Component} from 'react';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
message: "Hello World"
}
}
render() {
console.log("App render函数被调用");
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<button onClick={e => this.changeText()}>改变文本</button>
</div>
)
}
shouldComponentUpdate(nextProps, nextState) {
if (this.state.counter !== nextState.counter) {
return true;
}
return false;
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
changeText() {
this.setState({
message: "m2"
})
}
}
PureComponent
它是基于shouldComponentUpdate来做的性能优化,也就是当前组件的state未发生变化时,就不会执行后面的render操作。他也是通过shouldComponentUpdate来实现的。
基本原理:把新旧的props和新旧的state进行一个浅对比(源码中只比较了第一层,没做递归对比),如果没有发生变化,就不执行render函数。
import React, { PureComponent } from 'react';
// Header
function Header() {
console.log("Header被调用");
return <h2>我是Header组件</h2>
}
// Main
class Banner extends PureComponent {
render() {
console.log("Banner render函数被调用");
return <h3>我是Banner组件</h3>
}
}
function ProductList() {
console.log("ProductList被调用");
return (
<ul>
<li>商品列表1</li>
</ul>
)
}
class Main extends PureComponent {
render() {
console.log("Main render函数被调用");
return (
<div>
<Banner/>
<ProductList/>
</div>
)
}
}
// Footer
function Footer() {
console.log("Footer被调用");
return <h2>我是Footer组件</h2>
}
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
console.log("App render函数被调用");
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<Header/>
<Main/>
<Footer/>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
memo
memo的作用和PureComponent类似,只是memo作用于函数式组件,而PureComponent作用于class组件。
memo 可以用于包裹一个函数式组件,然后生成一个新的函数,这个函数会在执行render之前去浅对比一下新旧props,没有变化就不会执行render操作了。
import React, { PureComponent, memo } from 'react';
// Header
const MemoHeader = memo(function Header() {
console.log("Header被调用");
return <h2>我是Header组件</h2>
})
// Main
class Banner extends PureComponent {
render() {
console.log("Banner render函数被调用");
return <h3>我是Banner组件</h3>
}
}
const MemoProductList = memo(function ProductList() {
console.log("ProductList被调用");
return (
<ul>
<li>商品列表1</li>
</ul>
)
})
class Main extends PureComponent {
render() {
console.log("Main render函数被调用");
return (
<div>
<Banner/>
<MemoProductList/>
</div>
)
}
}
// Footer
const MemoFooter = memo(function Footer() {
console.log("Footer被调用");
return <h2>我是Footer组件</h2>
})
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
console.log("App render函数被调用");
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<MemoHeader/>
<Main/>
<MemoFooter/>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
ref拿到原生DOM和组件实例
通过createRef创建一个ref对象,然后在标签中设置ref属性为这个ref对象,之后就可以通过这个ref对象来拿到原生dom了。也可以拿到react组件实例对象。
另外也可以通过ref属性绑定回调的方式来拿到原生的dom对象。
import React, { PureComponent, createRef } from 'react';
class Counter extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
export default class App extends PureComponent {
constructor(props) {
super(props);
this.titleRef = createRef();
this.counterRef = createRef();
this.titleEl = null;
}
render() {
return (
<div>
{/* <h2 ref=字符串/对象/函数>Hello React</h2> */}
<h2 ref="titleRef">Hello React</h2>
{/* 目前React推荐的方式 */}
<h2 ref={this.titleRef}>Hello React</h2>
<h2 ref={arg => this.titleEl = arg}>Hello React</h2>
<button onClick={e => this.changeText()}>改变文本</button>
<hr/>
<Counter ref={this.counterRef}/>
<button onClick={e => this.appBtnClick()}>App按钮</button>
</div>
)
}
changeText() {
// 1.使用方式一: 字符串(不推荐, 后续的更新会删除)
this.refs.titleRef.innerHTML = "Hello Coderm2";
// 2.使用方式二: 对象方式
this.titleRef.current.innerHTML = "Hello JavaScript";
// 3.使用方式三: 回调函数方式
this.titleEl.innerHTML = "Hello TypeScript";
}
appBtnClick() {
this.counterRef.current.increment();
}
}
受控于非受控组件
受控组件就是你可以通过给表单元素绑定state上的值,然后再给他绑定React的合并事件。从而实现vue那样的响应式效果。
非受控组件就是你不给他绑定state上的值,也不给他绑定React的合并事件,从而这个表单元素就不受控了,如此一来,你只能通过ref来获取原生dom的数据了。
受控
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
fruits: "apple"
}
}
render() {
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<label htmlFor="username">
用户:
<input type="text"
id="username"
name="username"
onChange={e => this.handleChange(e)}
value={this.state.username}/>
</label>
<br/>
<label htmlFor="password">
密码:
<input type="text"
id="password"
name="password"
onChange={e => this.handleChange(e)}
value={this.state.password}/>
</label>
<br/>
<label htmlFor="fruits">
选择:
<select name="fruits"
id="fruits"
onChange={e => this.handleChange(e)}
value={this.state.fruits}>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
<option value="orange">橘子</option>
</select>
</label>
<br/>
<input type="submit" value="提交"/>
</form>
</div>
)
}
handleSubmit(event) {
event.preventDefault();
const {username, password, fruits} = this.state;
console.log(username, password, fruits);
}
handleChange(event) {
this.setState({
// 计算属性名
[event.target.name]: event.target.value
})
}
// handleUsernameChange(event) {
// this.setState({
// username: event.target.value
// })
// }
// handlePasswordChange(event) {
// this.setState({
// password: event.target.value
// })
// }
// handleFruitsChange(event) {
// this.setState({
// fruits: event.target.value
// })
// }
}
非受控
import React, { PureComponent, createRef } from 'react'
export default class App extends PureComponent {
constructor(props) {
super(props);
this.usernameRef = createRef();
}
render() {
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<label htmlFor="username">
用户:
<input type="text" id="username" ref={this.usernameRef}/>
</label>
<input type="submit" value="提交"/>
</form>
</div>
)
}
handleSubmit(event) {
event.preventDefault();
console.log(this.usernameRef.current.value);
}
}
高阶组件
高阶组件本质就是面向对象中装饰器的功能,给某一个类追加新功能,同时也会返回一个新的拓展过新功能的类。
基础版
import React, { PureComponent } from 'react'
class App extends PureComponent {
render() {
return (
<div>
App: {this.props.name}
</div>
)
}
}
// class 方式
function enhanceComponent(WrappedComponent) {
class NewComponent extends PureComponent {
render() {
return <WrappedComponent {...this.props}/>
}
}
NewComponent.displayName = "m2";
return NewComponent;
}
// function 方式
function enhanceComponent2(WrappedComponent) {
function NewComponent(props) {
return <WrappedComponent {...props}/>
}
NewComponent.displayName = "m2";
return NewComponent;
}
const EnhanceComponent = enhanceComponent2(App);
export default EnhanceComponent;
增强props
import React, { PureComponent } from 'react';
// 定义一个高阶组件
function enhanceRegionProps(WrappedComponent) {
return props => {
return <WrappedComponent {...props} region="中国"/>
}
}
class Home extends PureComponent {
render() {
return <h2>Home: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
}
}
class About extends PureComponent {
render() {
return <h2>About: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
}
}
const EnhanceHome = enhanceRegionProps(Home);
const EnhanceAbout = enhanceRegionProps(About);
class App extends PureComponent {
render() {
return (
<div>
App
<EnhanceHome nickname="coderm2" level={90}/>
<EnhanceAbout nickname="kobe" level={99}/>
</div>
)
}
}
export default App;
使用高阶组件使用Context来增强
使用前
import React, { PureComponent, createContext } from 'react';
// 创建Context
const UserContext = createContext({
nickname: "默认",
level: -1,
区域: "中国"
});
class Home extends PureComponent {
render() {
return (
<UserContext.Consumer>
{
user => {
return <h2>Home: {`昵称: ${user.nickname} 等级: ${user.level} 区域: ${user.region}`}</h2>
}
}
</UserContext.Consumer>
)
}
}
class About extends PureComponent {
render() {
return (
<UserContext.Consumer>
{
user => {
return <h2>About: {`昵称: ${user.nickname} 等级: ${user.level} 区域: ${user.region}`}</h2>
}
}
</UserContext.Consumer>
)
}
}
class App extends PureComponent {
render() {
return (
<div>
App
<UserContext.Provider value={{nickname: "m2", level: 90, region: "中国"}}>
<Home/>
<About/>
</UserContext.Provider>
</div>
)
}
}
export default App;
使用后
import React, { PureComponent, createContext } from 'react';
// 定义一个高阶组件
function withUser(WrappedComponent) {
return props => {
return (
<UserContext.Consumer>
{
user => {
return <WrappedComponent {...props} {...user}/>
}
}
</UserContext.Consumer>
)
}
}
// 创建Context
const UserContext = createContext({
nickname: "默认",
level: -1,
区域: "中国"
});
class Home extends PureComponent {
render() {
return <h2>Home: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
}
}
class About extends PureComponent {
render() {
return <h2>About: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
}
}
class Detail extends PureComponent {
render() {
return (
<ul>
<li>{this.props.nickname}</li>
<li>{this.props.level}</li>
<li>{this.props.region}</li>
</ul>
)
}
}
const UserHome = withUser(Home);
const UserAbout = withUser(About);
const UserDetail = withUser(Detail);
class App extends PureComponent {
render() {
return (
<div>
App
<UserContext.Provider value={{nickname: "m2", level: 90, region: "中国"}}>
<UserHome/>
<UserAbout/>
<UserDetail/>
</UserContext.Provider>
</div>
)
}
}
export default App;
高阶组件实现登录鉴权
import React, { PureComponent } from 'react';
class LoginPage extends PureComponent {
render() {
return <h2>LoginPage</h2>
}
}
function withAuth(WrappedComponent) {
const NewCpn = props => {
const {isLogin} = props;
if (isLogin) {
return <WrappedComponent {...props}/>
} else {
return <LoginPage/>
}
}
NewCpn.displayName = "AuthCpn"
return NewCpn;
}
// 购物车组件
class CartPage extends PureComponent {
render() {
return <h2>CartPage</h2>
}
}
const AuthCartPage = withAuth(CartPage);
export default class App extends PureComponent {
render() {
return (
<div>
<AuthCartPage isLogin={true}/>
</div>
)
}
}
通过高阶组件来劫持生命周期
使用前
import React, { PureComponent } from 'react';
class Home extends PureComponent {
// 即将渲染获取一个时间 beginTime
UNSAFE_componentWillMount() {
this.beginTime = Date.now();
}
// 渲染完成再获取一个时间 endTime
componentDidMount() {
this.endTime = Date.now();
const interval = this.endTime - this.beginTime;
console.log(`Home渲染时间: ${interval}`)
}
render() {
return <h2>Home</h2>
}
}
class About extends PureComponent {
// 即将渲染获取一个时间 beginTime
UNSAFE_componentWillMount() {
this.beginTime = Date.now();
}
// 渲染完成再获取一个时间 endTime
componentDidMount() {
this.endTime = Date.now();
const interval = this.endTime - this.beginTime;
console.log(`About渲染时间: ${interval}`)
}
render() {
return <h2>About</h2>
}
}
export default class App extends PureComponent {
render() {
return (
<div>
<Home />
<About />
</div>
)
}
}
使用后
import React, { PureComponent } from 'react';
function withRenderTime(WrappedComponent) {
return class extends PureComponent {
// 即将渲染获取一个时间 beginTime
UNSAFE_componentWillMount() {
this.beginTime = Date.now();
}
// 渲染完成再获取一个时间 endTime
componentDidMount() {
this.endTime = Date.now();
const interval = this.endTime - this.beginTime;
console.log(`${WrappedComponent.name}渲染时间: ${interval}`)
}
render() {
return <WrappedComponent {...this.props}/>
}
}
}
class Home extends PureComponent {
render() {
return <h2>Home</h2>
}
}
class About extends PureComponent {
render() {
return <h2>About</h2>
}
}
const TimeHome = withRenderTime(Home);
const TimeAbout = withRenderTime(About);
export default class App extends PureComponent {
render() {
return (
<div>
<TimeHome />
<TimeAbout />
</div>
)
}
}
class Person {
}
console.log(Person.name);
高阶组件forwardRef
这个组件支持代理转发ref
import React, { PureComponent, createRef, forwardRef } from 'react';
class Home extends PureComponent {
render() {
return <h2>Home</h2>
}
}
// 高阶组件forwardRef
const Profile = forwardRef(function(props, ref) {
return <p ref={ref}>Profile</p>
})
export default class App extends PureComponent {
constructor(props) {
super(props);
this.titleRef = createRef();
this.homeRef = createRef();
this.profileRef = createRef();
}
render() {
return (
<div>
<h2 ref={this.titleRef}>Hello World</h2>
<Home ref={this.homeRef}/>
<Profile ref={this.profileRef} name={"why"}/>
<button onClick={e => this.printRef()}>打印ref</button>
</div>
)
}
printRef() {
console.log(this.titleRef.current);
console.log(this.homeRef.current);
console.log(this.profileRef.current);
}
}
portals 门户组件
这个组件可以将当前节点挂载到父组件之外,类似于vue3中的Teleport组件。createPortal是ReactDOM的一个API。
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
class Modal extends PureComponent {
render() {
return ReactDOM.createPortal(
this.props.children,
document.getElementById("modal")
)
}
}
class Home extends PureComponent {
render() {
return (
<div>
<h2>Home</h2>
<Modal>
<h2>Title</h2>
</Modal>
</div>
)
}
}
export default class App extends PureComponent {
render() {
return (
<div>
<Home/>
</div>
)
}
}
Fragment
这个组件是一个空组件,类似于空标签的语法<></>
,不会占用任何标签位置。如果要用到key,那就只能写Fragment,不能写空标签的语法<></>
。
import React, { PureComponent, Fragment } from 'react';
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0,
friends: [
{name: "why", age: 18},
{name: "lilei", age: 20},
{name: "kobe", age: 25},
]
}
}
render() {
return (
<>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<div>
{
this.state.friends.map((item, index) => {
return (
<Fragment key={item.name}>
<div>{item.name}</div>
<p>{item.age}</p>
<hr/>
</Fragment>
)
})
}
</div>
</>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
StrictMode
给包裹层开启React的严格模式,这样一来你就不能用一些react中的不安全的生命周期,比如UNSAFE_componentWillMount
,你就得严格按照规范写。
它可以用来突出显示应用程序中潜在问题,StrictMode不会在界面中显示,它可以在开发的时候触发额外的检查和警告,但并不会影响生产构建。
主要作用:
识别不安全的生命周期
识别过时的ref API
识别废弃的findDOMNode方法
检查意外的副作用,比如把你组件constructor调用2次,看看会不会产生一些副作用,生产环境不会这样。
检查过时的context API,比如早起的Context是通过static属性声明的Context对象属性,然后通过getChildContext返回Context对象等方式来使用Context的。这种方式不好,已过时。
import React, { PureComponent, StrictMode } from 'react';
class Home extends PureComponent {
constructor(props) {
super(props);
console.log("home constrcutor");
}
// UNSAFE_componentWillMount() {
// console.log("home componentWillMount");
// }
render() {
return (
<div ref="title">
Home
</div>
)
}
}
class Profile extends PureComponent {
constructor(props) {
super(props);
console.log("profile constructor");
}
// UNSAFE_componentWillMount() {
// console.log("profile componentWillMount");
// }
render() {
return (
<div ref="title">
Profile
</div>
)
}
}
export default class App extends PureComponent {
render() {
return (
<div>
{/* 只检查Home组件及其后代组件 */}
<StrictMode>
<Home/>
</StrictMode>
{/* StrictMode 包裹层之外的不检查 */}
<Profile/>
</div>
)
}
}
React 中的样式
内联样式
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
color: "red"
}
}
render() {
const pStyle = {
color: this.state.color,
textDecoration: "underline"
}
return (
<div>
<h2 style={{fontSize: "50px", color: "red"}}>h2 title</h2>
<p style={pStyle}>p 是 一段描述</p>
</div>
)
}
}
普通样式css引入
import React, { PureComponent } from 'react';
import './style.css';
export default class Home extends PureComponent {
render() {
return (
<div className="home">
<h2 className="title">h2 标题</h2>
<div className="banner">
<span>广告位</span>
</div>
</div>
)
}
}
.home .title {
font-size: 25px;
color: blue;
}
.home .banner {
color: green;
}
CSS Module
转成js对象,直接.的方式用里面的class样式
import React, { PureComponent } from 'react';
import homeStyle from './style.module.css';
export default class Home extends PureComponent {
render() {
return (
<div className="home">
<h2 className={homeStyle.title}>我是home的标题</h2>
<div className={homeStyle.banner}>
<span>轮播图</span>
</div>
</div>
)
}
}
.home .title {
font-size: 25px;
color: blue;
}
.home .banner {
color: green;
}
styled-components
先安装这个依赖 styled-components
,然后用es6中的模板语法就能定义样式组件了。
import React, { PureComponent } from 'react';
import styled, { ThemeProvider } from 'styled-components';
const MyButton = styled.button`
padding: 10px 20px;
border-color: red;
color: red;
font-size: ${props => props.theme.fontSize};
`
const MyPrimaryButton = styled(MyButton)`
color: #fff;
background-color: ${props => props.theme.themeColor};
`
/**
* 特点:
* 1.props穿透
* 2.attrs的使用
* 3.传入state作为props属性
*/
const MyInput = styled.input.attrs({
placeholder: "m2",
bColor: "red"
})`
background-color: ${props => props.theme.themeColor};
color: ${props => props.color};
`
export default class App extends PureComponent {
render() {
return (
<ThemeProvider theme={{themeColor: "blue", fontSize: "30px"}}>
<MyInput type="password" color="red" />
<MyButton>普通的按钮</MyButton>
<MyPrimaryButton>主色的按钮</MyPrimaryButton>
</ThemeProvider>
)
}
}
总结
内容偏长,但是比较常见,也算是React实践必备吧。jsx本质、生命周期、组件嵌套、父子通信、组件通信、插槽机制、跨组件通信、setState、react性能优化、ref、受控和非受控组件、高阶组件、React中使用样式。后面还会有 antd、transition、redux、react-redux、middleware、reducer、router、hooks、next等等。