1. JSX
- JSX是一种JS和HTML混合的语法, 将组件的结构、数据甚至样式都聚合在一起定义组件
- JSX其实只是一种语法糖,最终会通过babeljs转译成
createElement语法,以下代码等价 - 元素是构成 React 应用的最小单位
- 元素用来描述你在屏幕上看到的内容
- React当中的元素事实上是普通的JS对象, ReactDOM来确保浏览器中的DOM数据和React元素保持一致
<h1 className="title" style={{color:'red'}}>hello</h1>
React.createElement("h1", {
className: "title",
style: {
color: 'red'
}
}, "hello");
createElement的结果
{
type:'h1',
props:{
className: "title",
style: {
color: 'red'
}
},
children:"hello"
}
2. 函数(定义的)组件
- 函数组件接收一个单一的
props对象并返回了一个React元素
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
3. 类(定义的)组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
5. 虚拟DOM
5.1 index.js
import React from './react';
import ReactDOM from './react-dom';
//let element = <h1 className="title" style={{color:'red',fontSize:'24px'}}></h1>
//let element = React.createElement('h1',{className:'title',style:{color:'red',fontSize:'50px'}},'hello');
//console.log(JSON.stringify(element));
//function Welcome(props){
// return React.createElement('h1',{className:'title'},props.title);
//}
class Welcome extends React.Component{
render(){
return React.createElement('h1',{className:'title'},this.props.title);
}
}
let element = React.createElement(Welcome,{title:'标题'});
ReactDOM.render(element, document.getElementById('root'));
5.2 react.js
import createElement from './element';
class Component{
static isReactComponent = true
constructor(props){
this.props = props;
}
}
export default {
createElement,Component
}
5.3 element.js
const ReactElement = function(type,props) {
const element = {
type: type,
props: props,
};
return element;
}
function createElement(type,config,children){
let propName;
const props = {};
for (propName in config) {
props[propName] = config[propName];
}
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
props.children = Array.prototype.slice.call(arguments,2);
}
return ReactElement(type,props);
}
export default createElement;
5.4 react-dom.js
function render(element,container){
if(typeof element == 'string'){
return container.appendChild(document.createTextNode(element))
}
let type,props;
type = element.type;
props = element.props;
if(type.isReactComponent){//如果为true说明它是一个类组件
element = new type(props).render();
type = element.type;
props = element.props;
}else if(typeof type =='function'){
element = type(props);
type = element.type;
props = element.props;
}
let domElement = document.createElement(type);
for(let propName in props){
if(propName === 'children'){
let children = props[propName];
children = Array.isArray(children)?children:[children];
children.forEach(child=>render(child,domElement));
}else if(propName === 'className'){
domElement.className = props[propName];
}else if(propName === 'style'){
let styleObj = props[propName];
/**
for(let attr in styleObj){
domElement.style[attr] = styleObj[attr];
}
*/
let cssText = Object.keys(styleObj).map(attr=>{
return `${attr.replace(/([A-Z])/g,function(){ return"-"+arguments[1]})}:${styleObj[attr]}`;
}).join(';');
domElement.style.cssText = cssText;
}else{
domElement.setAttribute(propName,props[propName]);
}
}
container.appendChild(domElement);
}
export default {render};
6. State
- 构造函数是唯一可以给
this.state赋值的地方 - 出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用
- 因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态
- 可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数
6.1 setState 异步还是同步?
- 有时异步:普通使用
- 有时同步: setTimeout、DOM事件
先给出答案: 有时表现出异步,有时表现出同步
- setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。
- setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
- setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。
解释:
- setState无所谓异步还是同步
- 看是否能命中batchUpdate机制
- 判断isBatchingUpdates
哪些能命中batchUpdate机制
- 生命周期(和它调用的函数)
- React中注册的事件(和它调用的函数)
- React可以管理的入口
哪些不能命中batchUpdate机制
- setTimeout setInterval等(和它调用的函数)
- 自定义的DOM事件(和它调用的函数)
- React管不到的入口
setState什么时候合并,什么时候不合并 有时合并:对象形式 有时不合并:函数形式
6.2 事务实现setState
- 源码
- 一个所谓的 Transaction 就是将需要执行的 method 使用 wrapper 封装起来,再通过 Transaction 提供的 perform 方法执行
- 而在 perform 之前,先执行所有 wrapper 中的 initialize 方法;perform 完成之后(即 method 执行后)再执行所有的 close 方法
- 一组 initialize 及 close 方法称为一个 wrapper
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
*
6.3 transaction
function setState() {
console.log('setState')
}
class Transaction {
constructor(wrappers) {
this.wrappers = wrappers;
}
perform(func) {
this.wrappers.forEach(wrapper=>wrapper.initialize())
func.call();
this.wrappers.forEach(wrapper=>wrapper.close())
}
}
let transaction = new Transaction([
{
initialize() {
console.log('before1');
},
close() {
console.log('after1');
}
},
{
initialize() {
console.log('before2');
},
close() {
console.log('after2');
}
}
]);
transaction.perform(setState);
6.4 index.js
class Transaction {
constructor(wrapper){
this.wrapper = wrapper;
}
perform(func){
this.wrapper.initialize();
func.call();
this.wrapper.close();
}
}
let batchingStrategy = {
isBatchingUpdates:false,
updaters:[],
batchedUpdates(){
this.updaters.forEach(updater => {
updater.component.updateComponent();
});
}
}
class Updater{
constructor(component){
this.component = component;
this.pendingStates = [];
}
addState(particalState){
this.pendingStates.push(particalState);
batchingStrategy.isBatchingUpdates?batchingStrategy.updaters.push(this):this.component.updateComponent();
}
}
let transaction = new Transaction({
initialize() {
batchingStrategy.isBatchingUpdates = true;
},
close() {
batchingStrategy.isBatchingUpdates = false;
batchingStrategy.batchedUpdates();
}
});
window.trigger = function(event,name){
let component = event.target.component;
transaction.perform(component[name].bind(component,event));
}
class Component{
constructor(props){
this.props = props;
this.$updater = new Updater(this);
}
createDOMFromString(domString){
const div = document.createElement('div');
div.innerHTML = domString;
return div.children[0];
}
setState(particalState){
this.$updater.addState(particalState);
}
updateComponent(){
let pendingStates = this.$updater.pendingStates;
pendingStates.forEach(particalState=>Object.assign(this.state,particalState));
this.$updater.pendingStates.length = 0;
let oldElement = this.domElement;
let newElement = this.renderElement();
oldElement.parentElement.replaceChild(newElement,oldElement);
}
renderElement(){
let renderString = this.render();
this.domElement = this.createDOMFromString(renderString);
this.domElement.component = this;
return this.domElement;
}
mount(container){
container.appendChild(this.renderElement());
}
}
class Counter extends Component{
constructor(props){
super(props);
this.state = {number:0};
}
increment(){
this.setState({number:this.state.number+1});
console.log(this.state);
this.setState({number:this.state.number+1});
console.log(this.state);
setTimeout(()=>{
this.setState({number:this.state.number+1});
console.log(this.state);
this.setState({number:this.state.number+1});
console.log(this.state);
},1000);
}
render(){
return (
`
<button id="counter-btn" onclick="trigger(event,'increment')">
${this.props.name}:${this.state.number}
</button>
`
)
}
}
7. Ref
- Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素
- 在 React 渲染生命周期时,表单元素上的 value 将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个 defaultValue 属性,而不是
value
7.1 为 dom元素添加Ref
import React from './react';
import ReactDOM from './react-dom';
class Sum extends React.Component{
constructor(props){
super(props);
this.a = React.createRef();//{current:null}
this.b = React.createRef();//{current:null}
this.result = React.createRef();//{current:null}
}
handleAdd = (event)=>{
let valueA = this.a.current.value;
let valueB = this.b.current.value;
this.result.current.value = valueA+valueB;
}
render(){
return (
<div>
<input ref={this.a}/>+<input ref={this.b}/>
<button onClick={this.handleAdd}>=</button>
<input ref={this.result}/>
</div>
)
}
}
ReactDOM.render(<Sum/>,document.getElementById('root'));
7.2 为 class 组件添加 Ref
- 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性
import React from './react';
import ReactDOM from './react-dom';
class TextInput extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
}
getFocus=()=>{
this.input.current.focus();
}
render() {
return <input ref={this.input}/>
}
}
class Form extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
}
getFocus = () => {
this.input.current.getFocus();
}
render() {
return (
<div>
<TextInput ref={this.input} />
<button onClick={this.getFocus}>获得焦点</button>
</div>
)
}
}
ReactDOM.render(<Form />, document.getElementById('root'));
7.3 函数组件添加Ref, useRef
import React from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [number, setNumber] = React.useState(0);
const numberRef = React.useRef();
const handleClick = () => {
setNumber(number + 1);
numberRef.current = number + 1;
console.log(numberRef.current);
}
React.useEffect(() => {
});
return (
<div>
<p>{number}</p>
<button onClick={handleClick}>+</button>
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
7.4 Ref转发
-
你不能在函数组件上使用 ref 属性,因为他们没有实例
-
Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧
-
Ref 转发允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件
class Form extends React.Component { constructor(props){ super(props); this.input = React.createRef(); } getFocus = () => { this.input.current.getFocus(); } render() { return ( <> <TextInput ref={this.input}/> <button onClick={this.getFocus}>获得焦点</button> </> ); } } // Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? function TextInput (){ return <input/> }
7.4.1 使用forwardRef
class Form extends React.Component {
constructor(props){
super(props);
this.input = React.createRef();
}
getFocus = () => {
this.input.current.focus();
}
render() {
return (
<>
<TextInput ref={this.input}/>
<button onClick={this.getFocus}>获得焦点</button>
</>
);
}
}
const TextInput = React.forwardRef((props,ref)=>(
<input ref={ref}/>
));
7.4.2 实现forwardRef
function createRef(){
return {
current:null
}
}
class Form extends React.Component {
constructor(props){
super(props);
this.input = createRef();
}
getFocus = () => {
this.input.current.focus();
}
render() {
return (
<>
<TextInput myref={this.input}/>
<button onClick={this.getFocus}>获得焦点</button>
</>
);
}
}
function forwardRef(funcComponent){
return function(props){
let ref = props.myref;
return funcComponent(props,ref);
}
}
const TextInput = forwardRef((props,ref)=>(
<input ref={ref}/>
));
8.生命周期
8.1 旧版生命周期
import React, {
Component,
useState,
useImperativeHandle,
useCallback,
useMemo,
useRef,
useEffect,
forwardRef,
useLayoutEffect
} from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component{ // 他会比较两个状态相等就不会刷新视图 PureComponent是浅比较
static defaultProps = {
name:'高途'
};
constructor(props){
super();
this.state = {number:0}
console.log('1.constructor构造函数')
}
componentWillMount(){ // 取本地的数据 同步的方式:采用渲染之前获取数据,只渲染一次
console.log('2.组件将要加载 componentWillMount');
}
componentDidMount(){
console.log('4.组件挂载完成 componentDidMount');
}
handleClick=()=>{
this.setState({number:this.state.number+1});
};
// react可以shouldComponentUpdate方法中优化 PureComponent 可以帮我们做这件事
shouldComponentUpdate(nextProps,nextState){ // 代表的是下一次的属性 和 下一次的状态
console.log('5.组件是否更新 shouldComponentUpdate');
return nextState.number%2;
// return nextState.number!==this.state.number; //如果此函数种返回了false 就不会调用render方法了
} //不要随便用setState 可能会死循环
componentWillUpdate(){
console.log('6.组件将要更新 componentWillUpdate');
}
componentDidUpdate(){
console.log('7.组件完成更新 componentDidUpdate');
}
render(){
console.log('3.render');
return (
<div>
<p>{this.state.number}</p>
{this.state.number>3?null:<ChildCounter n={this.state.number}/>}
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
class ChildCounter extends Component{
componentWillUnmount(){
console.log('组件将要卸载componentWillUnmount')
}
componentWillMount(){
console.log('child componentWillMount')
}
render(){
console.log('child-render')
return (<div>
{this.props.n}
</div>)
}
componentDidMount(){
console.log('child componentDidMount')
}
componentWillReceiveProps(newProps){ // 第一次不会执行,之后属性更新时才会执行
console.log('child componentWillReceiveProps')
}
shouldComponentUpdate(nextProps,nextState){
return nextProps.n%3==0; //子组件判断接收的属性 是否满足更新条件 为true则更新
}
}
ReactDOM.render(<Counter/>, document.getElementById('root'));
// defaultProps
// constructor
// componentWillMount
// render
// componentDidMount
// 状态更新会触发的
// shouldComponentUpdate nextProps,nextState=>boolean
// componentWillUpdate
// componentDidUpdate
// 属性更新
// componentWillReceiveProps newProps
// 卸载
// componentWillUnmount
8.2 新版生命周期
8.2.1 getDerivedStateFromProps
9. Context(上下文)
- 在某些场景下,你想在整个组件树中传递数据,但却不想手动地在每一层传递属性。你可以直接在 React 中使用强大的
contextAPI解决上述问题 - 在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
9.1 类组件使用
* 类组件中要想获取ThemeContext.Provider的value属性的值有两种方式
* 1.给类组件添加属性 static contextType=ThemeContext this.context获取value值
* 2.可以使用ThemeContext.Consumer组件获取到value值 Consumer组件的儿子是一个函数,函数的参数就是value值
* 其实Context实现原理就只是一个共享的变量,仅此而矣
import React from 'react';
import ReactDOM from 'react-dom';
let ThemeContext = React.createContext();
class Title extends React.Component {
static contextType = ThemeContext
render() {
return (
<div style={{ border: `5px solid ${this.context.color}` }}>
Title
</div>
)
}
}
class Header extends React.Component {
static contextType = ThemeContext
render() {
return (
<div style={{ border: `5px solid ${this.context.color}` }}>
Header
<Title />
</div>
)
}
}
class Content extends React.Component {
static contextType = ThemeContext
render() {
return (
<div style={{ border: `5px solid ${this.context.color}` }}>
Content
<button onClick={() => this.context.changeColor('red')}>变红</button>
<button onClick={() => this.context.changeColor('green')}>变绿</button>
</div>
)
}
}
class Main extends React.Component {
static contextType = ThemeContext
render() {
return (
<div style={{ border: `5px solid ${this.context.color}` }}>
Main
<Content />
</div>
)
}
}
class Panel extends React.Component {
state = { color: 'green' }
changeColor = (color) => {
this.setState({ color });
}
render() {
let value = { color: this.state.color, changeColor: this.changeColor };
//Provider提供者,它负责向下层所有的组件提供数据value
return (
<ThemeContext.Provider value={value}>
<div style={{ border: `5px solid ${this.state.color}`, width: 300 }}>
Panel
<Header />
<Main />
</div>
</ThemeContext.Provider>
)
}
}
ReactDOM.render(<Panel />, document.getElementById('root'));
9.2 函数组件使用
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
let ThemeContext = React.createContext('theme');
class Header extends Component {
render() {
return (
<ThemeContext.Consumer>
{
value => (
<div style={{ border: `5px solid ${value.color}`, padding: 5 }}>
header
<Title />
</div>
)
}
</ThemeContext.Consumer>
)
}
}
class Title extends Component {
static contextType = ThemeContext;
render() {
return (
<ThemeContext.Consumer>
{
value => (
<div style={{border: `5px solid ${value.color}` }}>
title
</div>
)
}
</ThemeContext.Consumer>
)
}
}
class Main extends Component {
static contextType = ThemeContext;
render() {
return (
<ThemeContext.Consumer>
{
value => (
<div style={{ border: `5px solid ${value.color}`, margin: 5, padding: 5 }}>
main
<Content />
</div>
)
}
</ThemeContext.Consumer>
)
}
}
class Content extends Component {
static contextType = ThemeContext;
render() {
return (
<ThemeContext.Consumer>
{
value => (
<div style={{border: `5px solid ${value.color}`, padding: 5 }}>
Content
<button onClick={() =>value.changeColor('red')} style={{color:'red'}}>红色</button>
<button onClick={() => value.changeColor('green')} style={{color:'green'}}>绿色</button>
</div>
)
}
</ThemeContext.Consumer>
)
}
}
class Page extends Component {
constructor() {
super();
this.state = { color: 'red' };
}
changeColor = (color) => {
this.setState({ color })
}
render() {
let contextVal = {changeColor: this.changeColor,color:this.state.color };
return (
<ThemeContext.Provider value={contextVal}>
<div style={{margin:'10px', border: `5px solid ${this.state.color}`, padding: 5, width: 200 }}>
page
<Header />
<Main />
</div>
</ThemeContext.Provider>
)
}
}
ReactDOM.render(<Page />, document.querySelector('#root'));
9.3 context实现
function createContext() {
let value;
class Provider extends React.Component {
constructor(props) {
super(props);
value = props.value
this.state = {};
}
static getDerivedStateFromProps(nextProps, prevState) {
value = nextProps.value;
return {};
}
render() {
return this.props.children;
}
}
class Consumer extends React.Component {
constructor(props) {
super(props);
}
render() {
return this.props.children(value);
}
}
return {
Provider,
Consumer
}
}
let ThemeContext = createContext('theme');
10. 高阶组件
- 高阶组件就是一个函数,传给它一个组件,它返回一个新的组件
- 高阶组件的作用其实就是为了组件之间的代码复用
const NewComponent = higherOrderComponent(OldComponent)
10.1 日志组件
10.1.1 手工实现
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
componentWillMount() {
this.start = Date.now();
}
componentDidMount() {
console.log((Date.now() - this.start) + 'ms')
}
render() {
return <div>App</div>
}
}
ReactDOM.render(<App />, document.getElementById('root'));
10.1.2 高阶组件
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
const logger = (WrappedComponent) => {
class LoggerComponent extends Component {
componentWillMount(){
this.start = Date.now();
}
componentDidMount(){
console.log((Date.now() - this.start)+'ms')
}
render () {
return <WrappedComponent />
}
}
return LoggerComponent;
}
let Hello = logger(props=><h1>hello</h1>);
ReactDOM.render(<Hello />, document.getElementById('root'));
10.2 多层高阶组件
10.2.1 从localStorage中加载
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
// 接受一个组件,和参数
const fromLocal = (WrappedComponent,name) =>{
class NewComponent extends Component{
constructor(){
super();
this.state = {value:null};
}
componentWillMount(){
let value = localStorage.getItem(name);
this.setState({value});
}
render(){
//
return <WrappedComponent value={this.state.value}/>
}
}
return NewComponent;
}
// 组件参数来源高阶组件内部
const UserName = ({value})=>(
<input defaultValue = {value}/>
);
// 高阶组件所需 username,生成组件所需要的props参数
const UserNameFromLocal = fromLocal(UserName,'username');
ReactDOM.render(<UserNameFromLocal />, document.getElementById('root'));
10.2.2 从ajax中加载
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
const fromLocal = (WrappedComponent,name) =>{
class NewComponent extends Component{
constructor(){
super();
this.state = {id:null};
}
componentWillMount(){
let id = localStorage.getItem(name);
this.setState({id});
}
render(){
return <WrappedComponent id={this.state.id}/>
}
}
return NewComponent;
}
const fromAjax = (WrappedComponent) =>{
class NewComponent extends Component{
constructor(){
super();
this.state = {value:{}};
}
componentDidMount(){
fetch(`/${this.props.id}.json`).then(response=>response.json()).then(value=>{
this.setState({value});
});
}
render(){
return <WrappedComponent value={this.state.value}/>
}
}
return NewComponent;
}
const UserName = ({value})=>{
return <input defaultValue = {value.username}/>;
}
const UserNameFromAjax = fromAjax(UserName);
const UserNameFromLocal = fromLocal(UserNameFromAjax,'id');
ReactDOM.render(<UserNameFromLocal />, document.getElementById('root'));
translate.json
{
"zhangsan": "张三"
}
10.3 属性代理
import React from 'react';
import ReactDOM from 'react-dom';
/**
* 高阶组件可以实现属性代理,给组件添加额外的属性,以实现特定的逻辑,可以实现逻辑的复用
* @param {} OldComponent
* @returns
*/
const withLoading = title => (OldComponent) => {
return class extends React.Component {
// constructor(props) {
// super(props);
// }
render() {
const state = {
show() {
console.log('show');
},
hide() {
console.log('hide');
}
}
return (
<OldComponent {...this.props} {...state} title={title} />
)
}
}
}
@withLoading('加载中...')
class Hello extends React.Component {
// constructor(props) {
// super(props);
// }
render() {
return (
<div>
<p>{this.props.title1}</p>
<button onClick={this.props.show}>显示</button>
<button onClick={this.props.hide}>隐藏</button>
</div>
)
}
}
const LoadingHello = withLoading('加载中')(Hello);
ReactDOM.render(<LoadingHello title1="标题" />, document.getElementById('root'));
10.4 反向继承
-
属性代理的时候 返回一个新组件,新组件会渲染老组件二个组件
-
在这个反向继承当前,我们返回一个新组件,新组件继承自老组件的,只有一个组件
-
一般来说子组件继承父组件,这个叫正向继承.如是正向的话先执行父亲再执行儿子
-
高阶组件可以反向继承
-
假如说你使用一个第三方库,源代码你无法修改,但又想扩展其功能
import React from 'react';
import ReactDOM from 'react-dom';
/**
* @param {} OldComponent
* @returns
*/
//假如说这是一个第三方组件,你只使用使用修改源码
//这种写法可以拦截生命周期,拦截渲染过程
class Button extends React.Component {
state = { name: '张三' }
componentWillMount() {
console.log('父亲 componentWillMount');
}
componentDidMount() {
console.log('父亲 componentDidMount');
}
render() {
console.log('父亲 render');
return (
<button name={this.state.name}>{this.props.title}</button >
)
}
}
const wrapper = (OldComponent) => {
return class extends OldComponent {
state = { number: 0 }
componentWillMount() {
console.log('儿子 componentWillMount');
super.componentWillMount();
}
componentDidMount() {
console.log('儿子 componentDidMount');
super.componentDidMount();
}
handleClick = () => {
this.setState({ number: this.state.number + 1 });
}
render() {
console.log('儿子 render');
let renderElement = super.render();
let newProps = {
...renderElement.props,
onClick: this.handleClick
}
return React.cloneElement(
renderElement,
newProps,
this.state.number
);
}
}
}
let WrappedButton = wrapper(Button);
ReactDOM.render(<WrappedButton title="按钮的标题" />, document.getElementById('root'));
11. render props
- render-props
render prop是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术- 具有 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑
- render prop 是一个用于告知组件需要渲染什么内容的函数 prop
- 这也是逻辑复用的一种方式
<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
11.1 使用例子
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div onMouseMove={this.handleMouseMove}>
<h1>移动鼠标!</h1>
<p>当前的鼠标位置是 ({this.state.x}, {this.state.y})</p>
</div>
);
}
}
11.2 render children
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.children(this.state)}
</div>
);
}
}
ReactDOM.render(< MouseTracker >
{
props=>(
<>
<h1>移动鼠标!</h1>
<p>当前的鼠标位置是 ({props.x}, {props.y})</p>
</>
)
}
</ MouseTracker>, document.getElementById('root'));
11.3 render属性
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
ReactDOM.render(< MouseTracker render={params=>(
<>
<h1>移动鼠标!</h1>
<p>当前的鼠标位置是 ({params.x}, {params.y})</p>
</>
)} />, document.getElementById('root'));
11.4 HOC
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
function withMouse(Component){
return (
(props)=><MouseTracker render={mouse=><Component {...props} {...mouse}/>}/>
)
}
let App = withMouse(props=>(
<>
<h1>移动鼠标!</h1>
<p>当前的鼠标位置是 ({props.x}, {props.y})</p>
</>
));
ReactDOM.render(<App/>, document.getElementById('root'));
14. 插槽(Portals)
- Portals 提供了一种很好的方法,将子节点渲染到父组件 DOM 层次结构之外的 DOM 节点。
ReactDOM.createPortal(child, container)
- 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 片段(fragment)
- 第二个参数(container)则是一个 DOM 元素
index.html
<div id="modal-root"></div>
index.js
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import './modal.css';
class Modal extends Component{
constructor() {
super();
this.modal=document.querySelector('#modal-root');
}
render() {
return ReactDOM.createPortal(this.props.children,this.modal);
}
}
class Page extends Component{
constructor() {
super();
this.state={show:false};
}
handleClick=() => {
this.setState({show:!this.state.show});
}
render() {
return (
<div>
<button onClick={this.handleClick}>显示模态窗口</button>
{
this.state.show&&<Modal>
<div id="modal" className="modal">
<div className="modal-content" id="modal-content">
内容
<button onClick={this.handleClick}>关闭</button>
</div>
</div>
</Modal>
}
</div>
)
}
}
ReactDOM.render(<Page/>,document.querySelector('#root'));
modal.css
.modal{
position: fixed;
left:0;
top:0;
right:0;
bottom:0;
background: rgba(0,0,0,.5);
display: block;
}
@keyframes zoom{
from{transform:scale(0);}
to{transform:scale(1);}
}
.modal .modal-content{
width:50%;
height:50%;
background: white;
border-radius: 10px;
margin:100px auto;
display:flex;
flex-direction: row;
justify-content: center;
align-items: center;
animation: zoom .6s;
}
15. 性能优化
15.1 使用React.Fragment
- 使用
React.Fragment来避免向 DOM 添加额外的节点
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Users extends React.Component {
render() {
return (
<React.Fragment>
<div>用户1</div>
<div>用户2</div>
</React.Fragment>
);
}
}
ReactDOM.render(<Users />, document.querySelector('#root'));
15.2. 使用 React.Lazy 延迟加载组件
React.Lazy帮助我们按需加载组件,从而减少我们应用程序的加载时间,因为只加载我们所需的组件。React.lazy接受一个函数,这个函数内部调用 import() 动态导入。它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。- React.Suspense 用于包装延迟组件以在加载组件时显示后备内容。
import React, { Component,lazy, Suspense } from 'react'
import ReactDOM from 'react-dom';
import Loading from './components/Loading';
const AppTitle = lazy(()=>import(/* webpackChunkName: "title" */'./components/Title'))
class App extends Component{
state = {visible:false}
show = ()=>{
this.setState({visible:true});
}
render() {
return (
<>
{this.state.visible&&(
<Suspense fallback={<Loading/>}>
<AppTitle/>
</Suspense>
)}
<button onClick={this.show}>加载</button>
</>
)
}
}
ReactDOM.render(<App />, document.querySelector('#root'));
15.3. 错误边界(Error Boundaries)
- 如果当一个组件异步加载下载js文件时,网络错误,无法下载 js 文件
- Suspense 无法处理这种错误情况, 在 react 中有一个 错误边界 (Error Boundaries)的概念,用来解决这种问题,它是利用了 react 生命周期的 componetDidCatch 方法来处理
- 有两种方式,一种是 生命周期 componentDidCatch 来处理错误,还有一种 是 静态方法 static getDerivedStateFromError 来处理错误,
- 请使用
static getDerivedStateFromError()渲染备用 UI ,使用 componentDidCatch() 打印错误信息。
import React, { Component,lazy, Suspense } from 'react'
import ReactDOM from 'react-dom';
import Loading from './components/Loading';
const AppTitle = lazy(()=>import(/* webpackChunkName: "title" */'./components/Title'))
class App extends Component{
state = {visible:false,isError: false}
show = ()=>{
this.setState({visible:true});
}
static getDerivedStateFromError(error) {
return { isError: true };
}
componentDidCatch (err, info) {
console.log(err, info)
}
render() {
if (this.state.isError) {
return (<div>error</div>)
}
return (
<>
{this.state.visible&&(
<Suspense fallback={<Loading/>}>
<AppTitle/>
</Suspense>
)}
<button onClick={this.show}>加载</button>
</>
)
}
}
ReactDOM.render(<App />, document.querySelector('#root'));
15.4. PureComponent
- 当一个组件的
props或state变更,React 会将最新返回的元素与之前渲染的元素进行对比,以此决定是否有必要更新真实的 DOM,当它们不相同时 React 会更新该 DOM。 - 如果渲染的组件非常多时可以通过覆盖生命周期方法 shouldComponentUpdate 来进行优化
- shouldComponentUpdate 方法会在重新渲染前被触发。其默认实现是返回 true,如果组件不需要更新,可以在
shouldComponentUpdate中返回 false 来跳过整个渲染过程。其包括该组件的 render 调用以及之后的操作 - PureComponent通过prop和state的浅比较来实现
shouldComponentUpdate
15.4.1 App.js
import React from 'react';
import {Button,message} from 'antd';
import PureComponent from './PureComponent';
export default class App extends PureComponent{
state = {
title:'计数器',
number:0
}
add = ()=>{
this.setState({number:this.state.number+parseInt(this.amount.value)});
}
render(){
console.log('App render');
return (
<div>
<Title2 title={this.state.title}/>
<Counter number={this.state.number}/>
<input ref={inst=>this.amount = inst}/>
<button onClick={this.add}>+</button>
</div>
)
}
}
class Counter extends PureComponent{
render(){
console.log('Counter render');
return (
<p>{this.props.number}</p>
)
}
}
//类组件可以用继承
class Title extends PureComponent{
render(){
console.log('Title render');
return (
<p>{this.props.title}</p>
)
}
}
//函数组件可以和memo
const Title2 = React.memo(props=>{
console.log('Title2 render');
return <p>{props.title}</p>;
});
//memo的实现
function memo(func){
class Proxy extends PureComponent{
render(){
return func(this.props);
}
}
return Proxy;
}
//memo的另一种实现 接收一个函数组件
function memo2(Func){
class Proxy extends PureComponent{
render(){
return <Func {...this.props}/>
}
}
return Proxy;
}
15.4.2 PureComponent
import React from 'react';
function shallowEqual(obj1,obj2){
if(obj1 === obj2){
return true;
}
if(typeof obj1 != 'object' || obj1 === null ||typeof obj2 != 'object' || obj2 === null ){
return false;
}
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if(keys1.length != keys2.length){
return false;
}
for(let key of keys1){
if(!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]){
return false;
}
}
return true;
}
export default class PureComponent extends React.Component{
isPureReactComponent = true
shouldComponentUpdate(nextProps,nextState){
return !shallowEqual(this.props,nextProps)||!shallowEqual(this.state,nextState)
}
}
15.6 减少渲染次数
- 把内联回调函数及依赖项数组作为参数传入
useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新 - 把创建函数和依赖项数组作为参数传入
useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
import React from 'react';
import ReactDOM from 'react-dom';
/**
* 1.让Child支持memo
*/
function Child({ data, handleClick }) {
console.log('Child render');
return (
<button onClick={handleClick}>{data.number}</button>
)
}
// let MemoChild = React.memo(Child);
function App() {
console.log('App render');
const [name, setName] = React.useState('zhufeng');
const [number, setNumber] = React.useState(0);
let data = React.useMemo(() => ({ number }), [number]);
let handleClick = React.useCallback(() => setNumber(number + 1), [number]);
return (
<div>
<input type="text" value={name} onChange={(event) => setName(event.target.value)} />
<Child data={data} handleClick={handleClick} />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
16. React Hooks
- Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
- 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以在现有的函数组件中使用 Hook
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
16.1 解决的问题
- 在组件之间复用状态逻辑很难,可能要用到render props和高阶组件,React 需要为共享状态逻辑提供更好的原生途径,Hook 使你在无需修改组件结构的情况下复用状态逻辑
- 复杂组件变得难以理解,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
- 难以理解的 class,包括难以捉摸的
this
16.2. 注意事项
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用
16.4. useState
-
useState 就是一个 Hook
-
通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state
-
useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并
-
useState 唯一的参数就是初始 state
-
返回一个 state,以及更新 state 的函数
- 在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
- setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列
const [state, setState] = useState(initialState);
16.5. useReducer
- useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
- 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等
16.5.1 基本用法
const [state, dispatch] = useReducer(reducer, initialArg, init);
const initialState = 0;
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {number: state.number + 1};
case 'decrement':
return {number: state.number - 1};
default:
throw new Error();
}
}
function init(initialState){
return {number:initialState};
}
function Counter(){
const [state, dispatch] = useReducer(reducer, initialState,init);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
)
}
16.5.2. useContext
- 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
- 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定
- 当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值
- useContext(MyContext) 相当于 class 组件中的
static contextType = MyContext或者<MyContext.Consumer> - useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context
const CounterContext = React.createContext();
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {number: state.number + 1};
case 'decrement':
return {number: state.number - 1};
default:
throw new Error();
}
}
function Counter(){
let {state,dispatch} = useContext(CounterContext);
return (
<>
<p>{state.number}</p>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
)
}
function App(){
const [state, dispatch] = useReducer(reducer, {number:0});
return (
<CounterContext.Provider value={{state,dispatch}}>
<Counter/>
</CounterContext.Provider>
)
}
17. effect
- 在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
- 使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道
- useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的
componentDidMount、componentDidUpdate和componentWillUnmount具有相同的用途,只不过被合并成了一个 API - 该 Hook 接收一个包含命令式、且可能有副作用代码的函数
useEffect(didUpdate);
17.1 通过class实现修标题
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
componentDidMount() {
document.title = `你点击了${this.state.number}次`;
}
componentDidUpdate() {
document.title = `你点击了${this.state.number}次`;
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={() => this.setState({ number: this.state.number + 1 })}>
+
</button>
</div>
);
}
}
在这个 class 中,我们需要在两个生命周期函数中编写重复的代码,这是因为很多情况下,我们希望在组件加载和更新时执行同样的操作。我们希望它在每次渲染之后执行,但 React 的 class 组件没有提供这样的方法。即使我们提取出一个方法,我们还是要在两个地方调用它。useEffect会在第一次渲染之后和每次更新之后都会执行
17.2 通过effect实现
import React,{Component,useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
function Counter(){
const [number,setNumber] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `你点击了${number}次`;
});
return (
<>
<p>{number}</p>
<button onClick={()=>setNumber(number+1)}>+</button>
</>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect 属于一次特定的渲染。
17.3 跳过 Effect 进行性能优化
- 如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可
- 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行
function Counter(){
const [number,setNumber] = useState(0);
// 相当于componentDidMount 和 componentDidUpdate
useEffect(() => {
console.log('开启一个新的定时器')
const $timer = setInterval(()=>{
setNumber(number=>number+1);
},1000);
},[]);
return (
<>
<p>{number}</p>
</>
)
}
17.4 清除副作用
- 副作用函数还可以通过返回一个函数来指定如何清除副作用
- 为防止内存泄漏,清除函数会在组件卸载前执行。另外,如果组件多次渲染,则在执行下一个 effect 之前,上一个 effect 就已被清除
import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [number, setNumber] = useState(0);
useEffect(() => {
console.log('开启一个新的定时器')
const $timer = setInterval(() => {
setNumber(number => number + 1);
}, 1000);
return () => {
console.log('销毁老的定时器');
clearInterval($timer);
}
});
return (
<>
<p>{number}</p>
</>
)
}
function App() {
let [visible, setVisible] = useState(true);
return (
<div>
{visible && <Counter />}
<button onClick={() => setVisible(false)}>stop</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
18 useRef
- useRef 返回一个可变的 ref 对象,其
.current属性被初始化为传入的参数(initialValue) - 返回的 ref 对象在组件的整个生命周期内保持不变
const refContainer = useRef(initialValue);
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Parent() {
let [number, setNumber] = useState(0);
return (
<>
<Child />
<button onClick={() => setNumber({ number: number + 1 })}>+</button>
</>
)
}
let input;
function Child() {
const inputRef = useRef();
console.log('input===inputRef', input === inputRef);
input = inputRef;
function getFocus() {
inputRef.current.focus();
}
return (
<>
<input type="text" ref={inputRef} />
<button onClick={getFocus}>获得焦点</button>
</>
)
}
ReactDOM.render(<Parent />, document.getElementById('root'));
19. forwardRef
- 将ref从父组件中转发到子组件中的dom元素上
- 子组件接受props和ref作为参数
function Child(props,ref){
return (
<input type="text" ref={ref}/>
)
}
Child = forwardRef(Child);
function Parent(){
let [number,setNumber] = useState(0);
const inputRef = useRef();
function getFocus(){
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<Child ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>获得焦点</button>
</>
)
}
20. useImperativeHandle
useImperativeHandle可以让你在使用 ref 时自定义暴露给父组件的实例值- 在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用
function Child(props,ref){
const inputRef = useRef();
useImperativeHandle(ref,()=>(
{
focus(){
inputRef.current.focus();
}
}
));
return (
<input type="text" ref={inputRef}/>
)
}
Child = forwardRef(Child);
function Parent(){
let [number,setNumber] = useState(0);
const inputRef = useRef();
function getFocus(){
console.log(inputRef.current);
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<Child ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>获得焦点</button>
</>
)
}
21. useLayoutEffect
- 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
- 可以使用它来读取 DOM 布局并同步触发重渲染
- 在浏览器执行绘制之前useLayoutEffect内部的更新计划将被同步刷新
- 尽可能使用标准的 useEffect 以避免阻塞视图更新
function LayoutEffect() {
const [color, setColor] = useState('red');
useLayoutEffect(() => {
alert(color);
});
useEffect(() => {
console.log('color', color);
});
return (
<>
<div id="myDiv" style={{ background: color }}>颜色</div>
<button onClick={() => setColor('red')}>红</button>
<button onClick={() => setColor('yellow')}>黄</button>
<button onClick={() => setColor('blue')}>蓝</button>
</>
);
}
22. 自定义 Hook
- 有时候我们会想要在组件之间重用一些状态逻辑
- 自定义 Hook 可以让你在不增加组件的情况下达到同样的目的
- Hook 是一种复用状态逻辑的方式,它不复用 state 本身
- 事实上 Hook 的每次调用都有一个完全独立的 state
- 自定义 Hook 更像是一种约定,而不是一种功能。如果函数的名字以 use 开头,并且调用了其他的 Hook,则就称其为一个自定义 Hook
22.1.自定义计数器
function useNumber(){
const [number,setNumber] = useState(0);
useEffect(() => {
console.log('开启一个新的定时器')
const $timer = setInterval(()=>{
setNumber(number+1);
},1000);
return ()=>{
console.log('销毁老的定时器')
clearInterval($timer);
}
});
return number;
}
function Counter1(){
let number1 = useNumber();
return (
<>
<p>{number1}</p>
</>
)
}
function Counter2(){
let number = useNumber();
return (
<>
<p>{number}</p>
</>
)
}
function App(){
return <><Counter1/><Counter2/></>
}
22.2 useParams
- 获取路由中的params
22.2.1 老版
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
function Post({ match }) {
let { title } = match.params;
return <div>{title}</div>;
}
ReactDOM.render(
<Router>
<div>
<Switch>
<Route path="/post/:title" component={Post} />
</Switch>
</div>
</Router>,
document.getElementById("root")
);
22.2.2.新版
import React from "react";
import ReactDOM from "react-dom";
+import { BrowserRouter as Router, Route, Switch, useParams } from "react-router-dom";
+function Post() {
+ let { title } = useParams();
+ return <div>{title}</div>;
+}
ReactDOM.render(
<Router>
<div>
<Switch>
+ <Route path="/post/:title"><Post /></Route>
</Switch>
</div>
</Router>,
document.getElementById("root")
);
22.3.useHistory
- 可以返回上一个网页
22.3.1 老版
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Switch, useHistory } from "react-router-dom";
function Post({ match, history }) {
let { title } = match.params;
return (
<div>
{title}
<hr />
<button type="button" onClick={() => history.goBack()}>
回去
</button>
</div>
);
}
function Home({ history }) {
return (
<>
<button type="button" onClick={() => history.push("/post/hello")}>
title
</button>
</>
)
}
ReactDOM.render(
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/post/:title" component={Post} />
</Switch>
</div>
</Router>,
document.getElementById("root")
);
22.3.2 新版
import React from "react";
import ReactDOM from "react-dom";
+import { BrowserRouter as Router, Route, Switch, useParams, useHistory } from "react-router-dom";
function Post() {
+ let { title } = useParams();
+ let history = useHistory();
return (
<div>
{title}
<hr />
+ <button type="button" onClick={() => history.goBack()}>
+ 回去
+ </button>
</div>
);
}
function Home() {
+ let history = useHistory();
return (
<>
+ <button type="button" onClick={() => history.push("/post/hello")}>
+ title
+ </button>
</>
)
}
ReactDOM.render(
<Router>
<div>
<Switch>
+ <Route exact path="/" component={Home} />
+ <Route path="/post/:title" component={Post} />
</Switch>
</div>
</Router>,
document.getElementById("root")
);