React学习笔记

79 阅读14分钟

一、React组件

1、无状态组件和有状态组件

  • 函数组件又叫做无状态组件,类组件又叫做有状态组件
  • 状态(state)即数据
  • 函数组件没有自己的状态,只负责数据展示(静态的)
  • 类组件有自己的状态,负责更新UI,让页面能够跟用户进行交互(动态)

比如:计数器中的值从 0开始,点击按钮,对值进行加1操作,计数器中的值就是 状态。

2、组件中的props特点

  • 可以给组件传递任意类型的数据

  • props是只读的对象,只能读取属性的值,无法修改对象

  • 注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props。

    class Hello extends React.Component {
      constructor() {
        super();
        console.log(this.props); // 这里拿不到props,想要拿到,需要在构造函数和super中写入props参数
      }
      // render中的 this.props能够拿到
      render() {
        return (
          <div>
            <span>姓名:{this.props.name}</span>
            <span>年龄:{this.props.age}</span>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Hello name="zhangsan" age={19} />,
      document.getElementById("root")
    );
    

3、组件之间通讯的三种方式

3.1、父组件传递数据给子组件

  • 父组件中要提供传递的state数据
  • 在父组件中,给子组件标签添加属性,值为state中的数据
  • 子组件中通过props接收父组件中传递的数据
// 父组件
class Parent extends React.Component {
  state = {
    ParentName: "张"
  };
  render() {
    return (
      <div>
        父组件姓名:{this.state.ParentName}
        <Children1 name={this.state.ParentName} />
      </div>
    );
  }
}

// 子组件

class Children1 extends React.Component {
  render() {
    console.log(this.props);
    return <div>子组件接收到父组件的数据:{this.props.name}</div>;
  }
}
ReactDOM.render(<Parent />, document.getElementById("root"));

3.2、子组件传递数据给父组件

思路:利用回调函数,父组件提供回调函数,子组件调用回调函数,并且把将要传递的数据作为回调函数的参数进行传递。

  • 父组件提供一个回调函数(用于接收数据)
  • 在父组件中,给子组件标签中添加属性,值为父组件中的回调函数(this.回调函数名)
  • 在子组件中声明一个函数,然后在子组件的函数中,通过该props调用父组件的回调函数
  • 将子组件的数据作为参数传递给父组件的回调函数
  • 在子组件中触发点击事件(或者其它事件),执行子组件中声明的函数
class Parent2 extends React.Component {
  state = {
    childName: " "
  };
  // 父组件提供一个回调函数(用于接收数据)
  ParentHandle = data => {
    console.log(data);
    this.setState({
      childName: data
    });
  };
  render() {
    return (
      <div>
        子组件传过来的值:{this.state.childName}
        <Children2 fn={this.ParentHandle} />
      </div>
    );
  }
}

class Children2 extends React.Component {
  state = {
    childName: "张三丰"
  };
  childHandle = () => {
    // 子组件通过该props调用回调函数
    // 将子组件的数据作为参数传递给回调函数
    this.props.fn(this.state.childName);
  };
  render() {
    return (
      <div>
        {/* 在子组件中触发点击事件(或者其它事件),执行子组件中声明的函数 */}
        <button onClick={this.childHandle}>点击我,把名字传递给父组件</button>
      </div>
    );
  }
}
ReactDOM.render(<Parent2 />, document.getElementById("root"));

3.3、兄弟组件之间的通讯

思想:把状态提升到兄弟组件最近的父组件中,让状态变成共享状态。(状态提升)

  • 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
    • 公共父组件要提供共享状态
    • 公共父组件要提供操作共享状态的方法
  • 兄弟组件中只要通过props接收状态或者调用操作状态的方法
class Parent3 extends React.Component {
  // 公共父组件要提供共享状态
  state = {
    count: 0
  };
  // 公共父组件要提供操作共享状态的方法
  parentHandle = () => {
    this.setState({
      count: this.state.count + 1
    });
  };
  render() {
    return (
      <div>
        <Child1 childCount={this.state.count} />
        <Child2 childFn={this.parentHandle} />
      </div>
    );
  }
}

class Child1 extends React.Component {
  render() {
    // 兄弟组件中只要通过props接收状态
    return <h1>当前计数器的值:{this.props.childCount}</h1>;
  }
}

class Child2 extends React.Component {
  render() {
    return (
      // 兄弟组件中只要通过props调用操作状态的方法
      <button
        onClick={() => {
          this.props.childFn();
        }}
      >
        加1
      </button>
    );
  }
}

ReactDOM.render(<Parent3 />, document.getElementById("root"));

4、Context

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props,(能够跨组件传递数据,解决了组件嵌套很深,传递数据繁琐的问题)

// Context (跨组件传递数据,解决嵌套层级较深的组件传值)

// 使用React.createContext 创建 Provider对象和 Consumer对象
// Provider对象:用来提供数据
// Consumer对象:用来消费数据
const { Provider, Consumer } = React.createContext();

class App extends React.Component {
  render() {
    return (
      <Provider value="color">
        <div>
          <Node />
        </div>
      </Provider>
    );
  }
}
class Node extends React.Component {
  render() {
    return (
      <div>
        <SubNode />
      </div>
    );
  }
}
class SubNode extends React.Component {
  render() {
    return (
      <div>
        <Child />
      </div>
    );
  }
}
class Child extends React.Component {
  render() {
    return <Consumer>{data => <div>{data}</div>}</Consumer>;
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

5.1 children属性

  • children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性
  • children属性与普通的props一样,值可以是任意值(文本、React元素、组件、函数)

children作为文本节点

class App extends React.Component {
  render() {
    return (
      <div>
        <Node>我是子节点</Node>
      </div>
    );
  }
}

class Node extends React.Component {
  render() {
    console.log(this.props);
    return (
      <div>
        组件中的文本内容是
        {this.props.children}
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));

image-20220228160144900


children作为组件

// children 作为组件
class App extends React.Component {
  render() {
    console.log(this.props);
    return <div>{this.props.children}</div>;
  }
}

class Node extends React.Component {
  render() {
    return <div>我是Node组件</div>;
  }
}

ReactDOM.render(
  <App>
    <Node />
  </App>,
  document.getElementById("root")
);

image-20220228160229099

children作为函数

// children作为函数
class App extends React.Component {
  render() {
    console.log(this.props);
    return <div>{this.props.children()}</div>;
  }
}

const handle = () => {
  console.log("hello");
};

ReactDOM.render(<App>{handle}</App>, document.getElementById("root"));

image-20220228160258763

5.2 props 校验

前提:

  • 对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
  • 如果传入的数据格式不对,可能会导致组件内部报错
  • 关键问题:组件的使用者不知道明确的错误原因

解决办法:

props 校验:允许在创建组件的时候,就制定 props 的类型、格式等

作用:捕获使用组件时因 props 导致的错误,给出明确的错误提示信息,增加组件的健壮性。

步骤:

  • 安装 prop-types包, yarn add prop-typesnpm i prop-types
  • 导入 prop-types包 import PropTypes from "prop-types";
  • 使用组件名.propTypes = {} 来给组件的props添加校验规则
  • 校验规则通过PropTypes对象来指定

函数形式

// 函数形式
const App = props => {
  const arr = props.colors;
  const lis = arr.map((item, index) => <li key={index}>{item}</li>);
  return (
    <div>
      <ul>{lis}</ul>;
    </div>
  );
};
// 注意:这里的 propTypes 中的开头的p是小写的。
App.propTypes = {
  // 意:这里的PropTypes 的 P 是大写的,是引入上面的。
  colors: PropTypes.array
};
ReactDOM.render(
  <App colors={["red", "pink"]} />,
  document.getElementById("root")
);

组件形式

// Props 校验
// 1、安装和导入 prop-types包
// 2、使用组件名.propTypes = {} 来给组件的props添加校验规则
// 3、校验规则通过PropTypes对象来指定

import PropTypes from "prop-types";

class App extends React.Component {
  //   state = {
  //     arr: this.props.colors
  //   };
  render() {
    const arr = this.props.colors;
    return (
      <div>
        <ul>
          {/* {this.state.arr.map((item, index) => (
            <li key={index}>{item}</li>
          ))} */}
          {arr.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
      </div>
    );
  }
}
// 注意:这里的PropTypes,跟函数形式不太一样。
App.defaultProps = {
  colors: PropTypes.array
};

ReactDOM.render(
  <App colors={["red", "pink"]} />,
  document.getElementById("root")
);

5.3 props的默认值

使用场景:比如:分页功能,用来显示条数

// props的默认值
const App = (props) => {
  return (
    <div>
      <h1>props的默认值是:{props.PageSize}</h1>
    </div>
  );
};
App.defaultProps = {
  PageSize: 30,
};
ReactDOM.render(<App />, document.getElementById("root"));

二、React组件的生命周期

组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的整个过程。

生命周期的每个阶段总是伴随着一些方法的调用,这些方法就是生命周期的钩子函数。

钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。

只有类组件才有生命周期,函数组件没有生命周期。

image-20211022215514025

1、挂载阶段的钩子函数

钩子函数触发时机作用
constructor创建组件时,最先执行1、初始化state
2、为事件处理程序绑定this
render每次组件渲染都会触发1、渲染UI<
2、注意:在render钩子函数中,不能调用setState()
因为setSate(),能够更新state,也能更新UI,只要调用setSate(),就会调用render更新UI,如果再render中再调用setSate()这样就会出现递归更新,导致报错)
componentDidMount组件挂载(完成DOM渲染)后执行1、发送网络请求
2、DOM操作

2、更新阶段的钩子函数

执行时机
1、组件接收到新的props会触发render,更新UI
2、调用setSate()会触发render,更新UI
3、调用forceUpdate()会强制触发render,更新UI

以上任意一种变化,组件都会重新渲染

钩子函数触发时机作用
render每次组件渲染都会触发1、渲染UI
componentDidUpdate组件更新(完成DOM渲染)后执行1、发送网络请求
2、DOM操作
3、注意:如果要调用setState()必须放在一个if条件中,因为在此钩子中调用 setState()也会递归更新
componentDidUpdate(prevProps){
	console.log(prevProps)
	console.log(this.props)
}
if(prevProps.name !=== this.props.name){
	this.setState({})
}

3、卸载阶段的钩子函数

钩子函数触发时机作用
componentWillUnmount组件卸载(从页面消失)执行清理,比如清理定时器

三、React组件复用

React组件复用:把多个组件中部分功能相似或者相同的状态或者逻辑进行复用。(复用:state和操作state的方法)。

复用的方式:

  • render props模式
  • 高阶组件(HOC)

1、render props模式

// 组件的复用
// render props模式
class Mouse extends React.Component {
  state = {
    x: 0,
    y: 0,
  };
  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY,
    });
  };
  // 监听鼠标的移动事件
  componentDidMount() {
    window.addEventListener("mouseover", this.handleMouseMove);
  }
  render() {
    // return null;
    return this.props.render(this.state);
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>render props 模式</h1>
        <Mouse
          render={(mouse) => {
            return (
              <div>
                鼠标的位置{mouse.x} {mouse.y}
              </div>
            );
          }}
        />
				
 				<Mouse
          render={(mouse) => {
            return (
              <div>
                <img
                  src={logo}
                  alt=""
                  style={{
                    position: "absolute",
                    left: mouse.x - 96,
                    top: mouse.y - 96,
                  }}
                />
              </div>
            );
          }}
        /> 

      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

用children替代render(推荐)

import logo from "./assets/logo192.png";
// 组件的生命周期

// class App extends React.Component {
//   constructor() {
//     super();
//   }

// }
// Props 校验
// 1、安装和导入 prop-types包
// 2、使用组件名.propTypes = {} 来给组件的props添加校验规则
// 3、校验规则通过PropTypes对象来指定

import PropTypes from "prop-types";
// 组件的复用
// render props模式
class Mouse extends React.Component {
  state = {
    x: 0,
    y: 0,
  };
  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY,
    });
  };
  // 监听鼠标的移动事件
  componentDidMount() {
    window.addEventListener("mouseover", this.handleMouseMove);
  }
  componentWillUnmount() {
    window.removeEventListener("mouseover", this.handleMouseMove);
  }
  render() {
    // return null;
    // return this.props.render(this.state);
    return this.props.children(this.state);
  }
}
Mouse.propTypes = {
  children: PropTypes.func.isRequired,
};

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>render props 模式</h1>
        {/* <Mouse
          render={(mouse) => {
            return (
              <div>
                鼠标的位置{mouse.x} {mouse.y}
              </div>
            );
          }}
        /> */}

        {/* <Mouse
          render={(mouse) => {
            return (
              <div>
                <img
                  src={logo}
                  alt=""
                  style={{
                    position: "absolute",
                    left: mouse.x - 96,
                    top: mouse.y - 96,
                  }}
                />
              </div>
            );
          }}
        /> */}

        <Mouse>
          {(mouse) => {
            return (
              <div>
                鼠标的位置{mouse.x} {mouse.y}
              </div>
            );
          }}
        </Mouse>
        <Mouse>
          {(mouse) => {
            return (
              <div>
                <img
                  src={logo}
                  alt=""
                  style={{
                    position: "absolute",
                    left: mouse.x - 96,
                    top: mouse.y - 96,
                  }}
                />
              </div>
            );
          }}
        </Mouse>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

2、高阶组件(HOC)

目的:实现状态逻辑复用

高阶组件(HOC,Higher-Order Components)是一个函数,接收一个要包装的组件,返回一个增强后的组件

高阶组件内部创建一个类组件,在这个类组件中,提供复用的状态逻辑代码,通过props将复用的状态传递给被包装组件WrappedComponent

const EnhancedComponent = higherOrderComponent(WrappedComponent);

高阶组件的使用步骤

1、创建一个函数,名称以hoc开头

2、指定函数参数,参数应该以大写字母开头(作为要渲染的组件)

3、在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回该类组件。

4、在该组件中,渲染参数组件,同时将状态通过props传递给参数组件

5、调用高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中

// 高阶组件
// 1、创建一个函数,名称以hoc开头
// 2、指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
const hocMouse = (WrappedComponent) => {
  // 3、在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回该类组件。
  class Mouse extends React.Component {
    // 鼠标状态
    state = {
      x: 0,
      y: 0,
    };
    handleMouseMove = (e) => {
      this.setState({
        x: e.clientX,
        y: e.clientY,
      });
    };
    // 控制鼠标状态的逻辑
    componentDidMount() {
      window.addEventListener("mousemove", this.handleMouseMove);
    }
    // 解绑事件
    componentWillUnmount() {
      window.removeEventListener("mousemove", this.handleMouseMove);
    }
    render() {
      return (
        <div>
         {/* 4、在该组件中,渲染参数组件,同时将状态通过props传递给参数组件 */}
          <WrappedComponent {...this.state}></WrappedComponent>
        </div>
      );
    }
  }
  // 返回类组件
  return Mouse;
};
// 测试高阶组件
const PagePosition = (props) => {
  return (
    <div>
      鼠标当前位置:x:{props.x},y:{props.y}
    </div>
  );
};
// 图片定位
const PageCatPosition = (props) => (
  <img
    src={logo}
    alt=""
    style={{
      position: "absolute",
      left: props.x - 96,
      top: props.y - 96,
    }}
  />
);
// 5、调用高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
const MousePage = hocMouse(PagePosition);
const PageCat = hocMouse(PageCatPosition);

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>高阶组件的运用</h1>
        {/* 渲染增强后的组件 */}
        <MousePage></MousePage>
        <PageCat></PageCat>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

高阶组件传递props

在改动代码:<WrappedComponent {...this.state} {...this.props}></WrappedComponent>

增加 {...this.props}

const hocMouse = (WrappedComponent) => {
  // 在内部创建类组件,在类组件中提供复用状态逻辑
  class Mouse extends React.Component {
    // 鼠标状态
    state = {
      x: 0,
      y: 0,
    };
    handleMouseMove = (e) => {
      this.setState({
        x: e.clientX,
        y: e.clientY,
      });
    };
    // 控制鼠标状态的逻辑
    componentDidMount() {
      window.addEventListener("mousemove", this.handleMouseMove);
    }
    // 解绑事件
    componentWillUnmount() {
      window.removeEventListener("mousemove", this.handleMouseMove);
    }
    render() {
      console.log(this.props);
      return (
        <div>
          <WrappedComponent {...this.state} {...this.props}></WrappedComponent>
        </div>
      );
    }
  }
  Mouse.displayName = `hocMouse${getDisplayName(WrappedComponent)}`;
  // 返回类组件
  return Mouse;
};

const getDisplayName = (WrappedComponent) => {
  return WrappedComponent.displayName || WrappedComponent.name || "Component";
};
// 测试高阶组件
const PagePosition = (props) => {
  console.log(props);
  return (
    <div>
      鼠标当前位置:x:{props.x},y:{props.y}
    </div>
  );
};
// 图片定位
const PageCatPosition = (props) => (
  <img
    src={logo}
    alt=""
    style={{
      position: "absolute",
      left: props.x - 96,
      top: props.y - 96,
    }}
  />
);
const MousePage = hocMouse(PagePosition);
const PageCat = hocMouse(PageCatPosition);
class App extends React.Component {
  render() {
    return (
      <div>
        <h1>高阶组件的运用</h1>
        {/* 渲染增强后的组件 */}
        <MousePage a="1"></MousePage>
        <PageCat></PageCat>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

四、React原理

1、setState() 说明

  • setState() 是异步更新数据的

  • 可以多次调用setState(),但是只会触发一次render(重新渲染)

class App extends React.Component {
  state = {
    num: 0,
  };
  handleAdd = () => {
    // 异步,多次调用this.setState,里面的num不能累加
    this.setState({
      num: this.state.num + 1,
    });
    this.setState({
      num: this.state.num + 1,
    });
    console.log(this.state.num);
  };
  render() {
    return (
      <div>
        <h2>当前的值为:{this.state.num}</h2>
        <button onClick={this.handleAdd}>+1</button>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));
  • 推荐语法
class App extends React.Component {
  state = {
    num: 0,
  };
  handleAdd = () => {
    // 推荐使用该语法:该语法也是异步的,但是多次调用this.setState,里面的num是累加的。
    // state:表示最新的state
    // props: 表示最新的props
    this.setState((state, props) => {
      return {
        num: state.num + 1,
      };
    });
    this.setState((state, props) => {
      return {
        num: state.num + 1,
      };
    });
    console.log(this.state.num);
  };
  render() {
    return (
      <div>
        <h2>当前的值为:{this.state.num}</h2>
        <button onClick={this.handleAdd}>+1</button>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));
  • setState() 的第二个参数

setState({},callback)

class App extends React.Component {
  state = {
    num: 0,
  };
  handleAdd = () => {
    // 推荐使用该语法:该语法也是异步的,但是多次调用this.setState,里面的num是累加的。
    // state:表示最新的state
    // props: 表示最新的props
    this.setState(
      (state, props) => {
        return {
          num: state.num + 1,
        };
      },
      () => {
        console.log(this.state.num); // 这里的num 跟页面中num值是同步的。
      }
    );

    console.log(this.state.num); // 0
  };
  // componentDidUpdate钩子函数的执行,提前于  this.setState执行
  componentDidUpdate() {
    console.log("componentDidUpdate", this.state.num); // 这里的num 跟页面中num值是同步的。
  }
  render() {
    return (
      <div>
        <h2>当前的值为:{this.state.num}</h2>
        <button onClick={this.handleAdd}>+1</button>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

结论:

  • componentDidUpdate钩子函数的执行,提前于 this.setState执行,this.setState的第二个参数的执行效果跟componentDidUpdate钩子函数的执行效果都是一样的。

2、组件更新机制

2.1 setState() 两个作用

  • 修改state
  • 更新UI(组件)

2.2 组件更新过程

父组件更新时,也会更新当前父组件中所有的子组件,不会影响父组件的父组件更新,也不会影响父组件的兄弟组件更新。

2.3 避免不必要的更新渲染

组件的更新机制:父组件的更新会引起子组件的更新。

上面的问题:当子组件没有任何变化时也会进行更新。

解决办法:使用钩子函数shouldComponentUpdate(nextProps, nextState)

使用shouldComponentUpdate()可以让React知道当前状态或属性的改变是否不影响组件的输出,通过返回值决定该组件是否重新渲染,默认返回ture表示重新渲染,返回false时不会重新渲染,而且该方法并不会在初始化渲染或当使用forceUpdate()时被调用。

使用 shouldComponentUpdate(nextProps, nextState) 中的 nextState,避免不必要的更新渲染

class App extends React.Component {
  state = {
    num: 0,
  };
  handleAdd = () => {
    this.setState(() => {
      return {
        num: Math.floor(Math.random() * 3),
      };
    });
  };
  shouldComponentUpdate(nextProps, nextState) {
    // nextState : 最新状态
    // this.state: 当前状态
    console.log("nextState", nextState, "this.state", this.state);
    /* if (nextState.num == this.state.num) {
      return false;
    }
    return true; */
    // 以上简写成
    return nextState.num !== this.state.num;
  }
  render() {
    console.log("重新渲染");
    return (
      <div>
        <h2>当前的值为{this.state.num}</h2>
        <button onClick={this.handleAdd}>加1</button>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate --> render)

使用 shouldComponentUpdate(nextProps, nextState) 中的 nextProps,避免不必要的更新渲染

class App extends React.Component {
  state = {
    num: 0,
  };
  handleAdd = () => {
    this.setState(() => {
      return {
        num: Math.floor(Math.random() * 3),
      };
    });
  };
  render() {
    return (
      <div>
        <NumData num={this.state.num}></NumData>
        <button onClick={this.handleAdd}>加1</button>
      </div>
    );
  }
}
class NumData extends React.Component {
  shouldComponentUpdate(nextProps) {
    console.log("nextProps", nextProps, "this.props", this.props);
    return nextProps.num !== this.props.num;
  }
  render() {
    console.log("重新渲染");
    return <h2>当前的值为{this.props.num}</h2>;
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

2.4 纯组件(PureComponent)

PureComponent中自动实现了shouldComponentUpdate钩子函数,更加方便

PureComponent原理:纯组件内部通过分别对比props、state的值,来决定是否重新渲染组件

class App extends React.PureComponent {
  state = {
    num: 0,
  };
  handleAdd = () => {
    this.setState(() => {
      return {
        num: Math.floor(Math.random() * 3),
      };
    });
  };

  render() {
    console.log("重新渲染");
    return (
      <div>
        <h2>当前的值为{this.state.num}</h2>
        <button onClick={this.handleAdd}>加1</button>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));
class App extends React.Component {
  state = {
    num: 0,
  };
  handleAdd = () => {
    this.setState(() => {
      return {
        num: Math.floor(Math.random() * 3),
      };
    });
  };

  render() {
    console.log("重新渲染");
    return (
      <div>
        <NumData num={this.state.num}></NumData>
        <button onClick={this.handleAdd}>加1</button>
      </div>
    );
  }
}
class NumData extends React.PureComponent {
  render() {
    return <h2>当前的值为{this.props.num}</h2>;
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

纯组件内部对比的是 shallow compare(浅层对比),对于数值类型是可以,但是对于引用类型就会出现问题

注意:props或state属性值为引用类型时,应该创建新数据,不要直接修改原数据

class App extends React.PureComponent {
  state = {
    obj: {
      num: 0,
    },
  };
  handleAdd = () => {
    // 创建新的对象
    const newObj = { ...this.state.obj, num: Math.floor(Math.random() * 3) };
    this.setState(() => {
      return {
        obj: newObj,
      };
    });
  };

  render() {
    console.log("重新渲染");
    return (
      <div>
        <h2>当前的值为{this.state.obj.num}</h2>
        <button onClick={this.handleAdd}>加1</button>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

同时注意:也不要使用数组的push/unshift等直接修改当前数组的方法,这些都是在修改了原数组,没有创建新的数组,应该使用concat或者slice等这些返回新数组的方法。

this.setState({
list:[...this.state.list,{newData}]
})

五、React路由

路由是一套映射规则,在React中,是URL路径与组件的对应关系,其实就是配置路径与组件的配对,让用户从一个视图,导航到另外一个视图中

1、基本用法

// 引入React路由
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

const About = () => {
  return <div>关于页面</div>;
};

class App extends React.Component {
  render() {
    return (
      // 使用Router组件包裹整个应用,Router组件只需要使用一次即可
      <Router>
        <div>
          <h2>React路由</h2>
          {/* 指定路由入口 */}
          <Link to="/about">跳转至关于页面</Link>
          {/* 指定路由出口 */}
          <Route path="/about" component={About}></Route>
        </div>
      </Router>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

2、编程式导航

class Login extends React.Component {
  handleLogin = () => {
    // 编程式导航 push跳转到某个页面
    this.props.history.push("/home");
  };
  render() {
    return (
      <div>
        <div>登录页面欢迎您</div>
        <br />
        <button onClick={this.handleLogin}>登录</button>
      </div>
    );
  }
}
const HomePage = (props) => {
  const handleBack = () => {
    // 编程式导航 go(-1)返回
    props.history.go(-1);
  };
  return (
    <div>
      <h2>首页</h2>
      <button onClick={handleBack}>退出</button>
    </div>
  );
};

const App = () => {
  return (
    <Router>
      <div>
        <h2>注册页面</h2>
        <Link to="/login">去登录</Link>
        <br />
        <br />
        <Route path="/login" component={Login} />
        <Route path="/home" component={HomePage} />
      </div>
    </Router>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));

3、默认路由

const About = () => {
  return <div>关于页面</div>;
};

class App extends React.Component {
  render() {
    return (
      <Router>
        <div>
          <h2>React路由</h2>
          {/* 默认路由 */}
          <Route path="/" component={About}></Route>
        </div>
      </Router>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));

4、模糊匹配和精准匹配

在React中,React路由默认是模糊匹配模式,想要精准匹配如下所示,

给默认路由添加 exact属性

<Route exact path="/" component={About}></Route>