setState是异步更新数据
// App.js
import React from 'react'
class App extends React.Component{
state = {
count:1
}
handleClick = (e)=>{
this.setState({
count: this.state.count + 1
})
// 输出旧值
console.log(this.state.count)
}
handleClick2 = (e)=>{
this.setState({
count: this.state.count + 1
})
// 输出旧值
console.log(this.state.count)
// 旧值+1
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
}
render(){
return (
// 多次调用setState,只会render 1次
console.log("render");
<div>
{this.state.count}<br/>
<button onClick={this.handleClick}>add+1</button><br/>// + 1
<button onClick={this.handleClick2}>add+1+1</button>// 仍是 +1
</div>
)
}
}
export default App

handleClick = (e)=>{
// 也是异步的
this.setState((state,props)=>{
return{
count: state.count + 1
}
})
this.setState((state,pros)=>{
console.log("第二次调用",state.count)
return{
count: state.count + 1
}
})
// 同时因为异步,该输出会先于前面
console.log(this.state.count)
}

handleClick = (e)=>{
this.setState(
(state,props)=>{
return{
count: state.count + 1
}
},
()=>{
console.log("状态更新后的值",this.state.count)
}
)
console.log("状态更新前的值",this.state.count)
}

setState作用
组件更新会更新自己以及自己的所有子节点。
父节点和兄弟节点不会更新。
JSX->createElement()->React元素
JSX
const element = (
<h1 className="greeting">
Hello JSX!
</h1>
)
createElement()
const element = React.createElement(
'h1',
{className:'greeting'},
'Hello JSX!'
)
React元素
const element = {
type:'h1',
props:{
className: 'greeting'
children: 'Hello JSX!'
}
}
state:只存储与组件渲染相关的数据
不做渲染的数据不要放在state中,直接放在this之中即可
组件的更新策略,会导致不必要的渲染也会发生(子组件没有变化)
使用 shouldComponentUpdate(nextProps,nextState)
// App.js
import React from 'react'
class App extends React.Component{
state = {
count:1
}
handleClick = (e)=>{
this.setState((state,props)=>{
return {
count: state.count + 1
}
},
()=>{
console.log(this.state.count)
}
)
}
/**
* @param nextProps 最新的props
* @param nextState 最新的状态
*/
shouldComponentUpdate(nextProps,nextState){
console.log(nextProps)
console.log(nextState)
return false
}
render(){
return (
<div>
{this.state.count}<br/>
<button onClick={this.handleClick}>add+1</button>
</div>
)
}
}
export default App
随机数示例略。
对于可能有多个值会变化的情况,我们可以将它们分到子组件去,然后通过子组件去检测是否需要更新。同时这样更新的范围也会小很多。
React.PureComponent与React.Component功能相似
PureComponent自动实现了shouldComponentUpdate,不需要手动比较
内部通过分别比较前后两侧props和state的值,来决是否重新渲染组件
示例略。
(可以通过:浅拷贝和深拷贝来理解)
官方文档说明
大部分情况下,你可以使用 React.PureComponent 来代替手写 shouldComponentUpdate。但它只进行浅比较,所以当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。当数据结构很复杂时,情况会变得麻烦。
例如,你想要一个 ListOfWords 组件来渲染一组用逗号分开的单词。它有一个叫做 WordAdder 的父组件,该组件允许你点击一个按钮来添加一个单词到列表中。以下代码并不正确:
class ListOfWords extends React.PureComponent {
render() {
return <div>{this.props.words.join(',')}</div>;
}
}
class WordAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// 这部分代码很糟,而且还有 bug
const words = this.state.words;
// 不会生成新的数组words
words.push('marklar');
this.setState({words: words});
}
render() {
return (
<div>
<button onClick={this.handleClick} />
<ListOfWords words={this.state.words} />
</div>
);
}
}
问题在于 PureComponent 仅仅会对新老 this.props.words 的值进行简单的对比。由于代码中 WordAdder 的 handleClick 方法改变了同一个 words 数组,使得新老 this.props.words 比较的其实还是同一个数组。即便实际上数组中的单词已经变了,但是比较结果是相同的。可以看到,即便多了新的单词需要被渲染, ListOfWords 却并没有被更新。
可以用不可变数据解决。(也就是避免产生对象、引用)可以参考官方文档的不可变数据部分。
用如下方式获取一个新的数组words
handleClick() {
this.setState(state => ({
words: [...state.words, 'marklar'],
}));
};
或
handleClick() {
this.setState(state => ({
words: state.words.concat(['marklar'])
}));
}
用如下方式获取一个新的对象colormap
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
}
如下方法不会生成新的对象colormap
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
function updateColorMap(colormap) {
colormap.right = 'blue';
}
React更新视图的思想是:只要state变化就重新渲染和视图
DOM元素需要更新时,也是部分更新。
React使用虚拟DOM配合Diff算法做到的。
虚拟DOM本质上是一个JS对象,用来描述你希望在屏幕上看到的(UI)内容。也即是我们前面提到的React元素。
render方法调用,并不意味者浏览器重新渲染,仅仅说明进行DIFF
现代前端应用大多数都是SPA(单页面应用程序),也就是只有一个HTML页面程序。
为了有效的使用单个页面来管理原来多个页面功能,前端路由应运而生。
注意,视频中使用的是V5版本,笔者这里使用的V6,因此将不会完全按照视频中的来做。
npm install react-router-dom
可参考文档-第三方库-路由
也可以使用视频中的版本
npm install react-router-dom@5.2.0
导入路由的三个核心组件: Router/Route/Link 以及V6的Routes
import React from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter as Router,Route,Routes,Link} from 'react-router-dom'
const First = ()=>(
<p>页面一内容</p>
)
const App = () => {
return(
<Router>
<div className="App" >
<h1>React路由基础</h1>
{/* 指定入口,导航 */}
<Link to="/first">页面一</Link>
<Routes>
{/* 指定路由出口,内容 */}
<Route path='/first' element={<First />}/>
</Routes>
</div>
</Router>
)
}
const reactDom = ReactDOM.createRoot(document.getElementById('root'))
reactDom.render(<App/>)

常用的Router:HashRouter和BrowserRouter
HashRouter使用URL的哈希值实现

老版本:Router-Switch-Route
新版本:Router-Routes-Route
<Router>
<div className="App" >
<Routes>
{/* 指定路由出口,内容 */}
<Route path='/first' element={<First />}/>
</Routes>
<h1>React路由基础</h1>
{/* 指定入口,导航 */}
<Link to="/first">页面一</Link>
</div>
</Router>

import React from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter as Router,Route,Routes,Link} from 'react-router-dom'
import Home from './component/Home';
import Login from './component/Login';
const App = () => {
return(
<Router>
<div className="App" >
<h1>React路由基础</h1>
{/* 指定入口,导航 */}
<Link to="/login">登录页面</Link>
<Routes>
{/* 指定路由出口,内容 */}
<Route path='/login' element={<Login />}/>
<Route path='/home' element={<Home />}/>
</Routes>
</div>
</Router>
)
}
const reactDom = ReactDOM.createRoot(document.getElementById('root'))
reactDom.render(<App/>)
import React from 'react'
class Home extends React.Component{
render(){
return (<div>后台页面</div>)
}
}
export default Home
注意,新版本中,没有了history,因此navigate作为代替,但是它是一种hook,react中类组件不能用hook(因为hook目的就是为了代替类)。因此我们只能用函数的方式完成。
同时,其中填入负数,也就是回退多少页。它有第二个参数,用于传参。
import React from 'react'
import { useNavigate } from 'react-router-dom'
function Login(){
const navigate = useNavigate();
const handleLogin = () =>{
//...
// 编程式导航跳转
navigate('/home')
}
return(
<div>
<div>登录页面:</div>
<button onClick={()=>{handleLogin()}}>登录</button>
</div>
)
}
export default Login


进入页面时匹配的路由
就是 path:/
此处login就是默认路由
<Routes>
{/* 指定路由出口,内容 */}
<Route path='/' element={<Login />}/>
<Route path='/home' element={<Home />}/>
</Routes>
如下(笔者这里没有遇到该问题,可能是脚手架或者版本已经处理了该问题。
一般情况下,可以加一个exact
| path(Routes) | to(link) |
|---|---|
| / | 会匹配所有的路由 |
| /first | 会匹配 /first 、/first/a |
<Routes>
{/* 指定路由出口,内容 */}
<Route path='/' element={<Home />}/>
<Route path='/login' element={<Login />}/>
</Routes>
[1]黑马程序员前端React视频教程,react零基础入门原理详解到好客租房项目实战
[2]官方文档
[3]第三方库-路由