五、React原理揭秘
目标
◆ 能够知道setState()更新数据是异步的
◆ 能够知道JSX语法的转化过程
◆ 能够说出React组件的更新机制
◆ 能够对组件进行性能优化
◆ 能够说出虚拟DOM和Diff算法
目录
◆ setState() 的说明
◆ JSX语法的转化过程
◆ 组件更新机制
◆ 组件性能优化
◆ 虚拟DOM和Diff算法
1.setState() 的说明
1.1 更新数据
● setState()是异步更新数据的
● 注意:使用该语法时,后面的setState()不要依赖于前面的setState()
● 可以多次调用setState() , 只会触发一次重新渲染
1.2推荐语法
● 推荐:使用**setState((state, props) => {})**语法
● 参数state :表示最新的state
● 参数props :表示最新的props
1.3第二个参数
● 场景:在状态更新(页面完成重新渲染)后应即执行某个操作
● 语法:setState(updater[,callback])
2. JSX语法的转化过程
● JSX仅仅是createElement(方法的语法糖(简化语法)
● JSX语法被@babel/preset-react插件编译为createElement()方法
● React元素:是一个对象,用来描述你希望在屏幕上看到的内容
3. 组件更新机制
● setState()的两个作用: 1. 修改state 2.更新组件(UI )
● 过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)
4.组件性能优化
4.1减轻state
● 减轻state :只存储跟组件渲染相关的数据(比如: count/列表数据/ loading等)
● 注意:不用做渲染的数据不要放在state中,比如定时器id等
● 对于这种需要在多个方法中用到的数据,应该放在this中
4.2 避免不必要的重新渲染
● 组件更新机制:父组件更新会引|起子组件也被更新,这种思路很清晰
● 问题:子组件没有任何变化时也会重新渲染
● 如何避免不必要的重新渲染呢?
● 解决方式:使用钩子函数shouldComponentUpdate(nextProps, nextState)
● 作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染, false表示不重新渲染
● 触发时机:更新阶段的钩子函数,组件重新渲染前执行( shouldC omponentUpdate --> render )
● 案例:随机数
4.3 纯组件
● 纯组件: PureComponent与React.Component功能相似
● 区别: PureComponent内部自动实现了shouldComponentUpdate钩子,不需要手动比较
● 原理:纯组件内部通过分别对比前后两次props和state的值,来决定是否重新渲染组件
● 说明:纯组件内部的对比是shallow compare (浅层对比)
● 对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)
● 对于引用类型来说:只比较对象的引用(她址)是否相同
● 注意: state或props中属性值为引用类型时,应该创建新数据,不要直接修改原数据! ( 示例)
5. 虚拟DOM和Diff算法
● React 更新视图的思想是:只要state变化就重新渲染视图
● 特点:思路非常清晰
● 问题:组件中只有一一个 DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?不是
● 理想状态:部分更新,只更新变化的地方。
● 问题: React是如何做到部分更新的? 虚拟DOM配合Diff 算法
虚拟DOM :本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)。
执行过程
-
初次渲染时, React会根据初始state ( Model) ,创建一 个虚拟DOM对象(树)。
-
根据虚拟DOM生成真正的DOM ,渲染到页面中。
-
当数据变化后( setState() ,重新根据新的数据,创建新的虚拟DOM对象(树)。
-
与上-次得到的虚拟DOM对象,使用Diff算法对比(找不同) ,得到需要更新的内容。
-
最终, React只将变化的内容更新( patch )到DOM中,重新渲染到页面。
代码演示
● 组件render()调用后,根据状态和JSX结构生成虚拟DOM对象
● 示例中,只更新p元素的文本节点内容
6. 总结–React原理揭秘
-
工作角度:应用第一,原理第二。
-
原理有助于更好地理解React的自身运行机制。
-
setState()异步更新数据。
-
父组件更新导致子组件更新,纯组件提升性能。
-
思路清晰简单为前提,虚拟DOM和Diff保效率。
-
虚拟DOM → state + JSX。
-
虚拟DOM的真正价值从来都不是性能。
-
虚拟DOM最大价值:让React脱离了浏览器的束缚(虚拟DOM:JS的一个对象)
六、React路由基础
目标
◆ 能够说出React路由的作用
◆ 能够掌握react-router-dom的基本使用
◆ 能够使用编程式导航跳转路由
◆ 能够知道React路由的匹配模式
目录
◆ React路由介绍
◆ 路由的基本使用
◆ 路由的执行过程
◆ 编程式导航
◆ 默认路由
◆ 匹配模式
1. React路由介绍
现代的前端应用大多都是SPA (单页应用程序) ,也就是只有一一个HTML页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生。
● 前端路由的功能:让用户从一一个视图(页面)导航到另-一个视图(页面)
● 前端路由是一套映射规则,在React中,是URL路径与组件的对应关系
● 使用React路由简单来说,就是配置路径和组件(配对)
2. React路由的基本使用
2.1 使用步骤
-
安装: yarn add react- router-dom/npm i react-router-dom
-
导入路由的三个核心组件: Router / Route / Link
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
-
使用Router组件包裹整个应用(重要)
<Router> <div className="App"> // ... 省略页面内容 </div> </Router>
-
使用Link组件作为导航菜单(路由入口)
<Link to="/first">页面一</Link>
-
使用Route组件配置路由规则和要展示的组件(路由出口)
const First = () => <p>页面一的页面内容</p> <Router> <div className="App"> <Link to="/first">页面一</Link> <Route path="/first" component={First}></Route> </div> </Router>
-
总代码
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router, Route, Link } from 'react-router-dom' const First = () =>(<p>页面一内容</p>) const App = ()=>( <Router> <div> <h1>路由页面一</h1> <Link to="/first">页面一</Link> <Route path="/first" component={First}></Route> </div> </Router> ) ReactDOM.render(<App/>,document.getElementById('root'))
2.2 常用组件说明
● Router组件:包裹整个应用,一个React应用只需要使用一次
● 两种常用Router : HashRouter和BrowserRouter
● HashRouter :使用URL的哈希值实现(localhost:3000/#/first )
● (推荐) BrowserRouter :使用H5的history API实现(localhost:3000/first )
● Link 组件:用于指定导航链接(a标签)
// to属性:浏览器地址栏中的pathname (location.pathname)
<Link to="/first">页面一</Link>
● Route组件:指定路由展示组件相关信息
// path属性: 路由规则
// component属性: 展示的组件
// Route组件写在哪,渲染出来的组件就展示在哪
<Route path="/first" component={First}></Route>
3. 路由的执行过程
- 点击Link组件( a标签) ,修改了浏览器地址栏中的url。
- React 路由监听到地址栏url的变化。
- React 路由内部遍历所有Route组件,使用路由规则( path )与pathname进行匹配。
- 当路由规则( path )能够匹配地址栏中的pathname时,就展示该Route组件的内容。
4. 编程式导航
● 场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如何实现?
● 编程式导航:通过JS代码来实现页面跳转
● history是React路由提供的,用于获取浏览器历史记录的相关信息
● push(path) :跳转到某个页面,参数path表示要跳转的路径
● go(n) :前进或后退到某个页面,参数n表示前进或后退页面数量(比如: -1表示后退到上一页)
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
class Login extends React.Component{
handleLogin=()=>{
this.props.history.push('/home')
}
render(){
return(
<div>
<h2>登录页面</h2>
<button onClick={this.handleLogin}>登录</button>
</div>
)
}
}
const Home =(props)=>{
const handleBack =()=>{
props.history.go(-1)
}
return(
<div>
<h2>首页</h2>
<button onClick={handleBack}>去登录页</button>
</div>
)
}
class App extends React.Component{
render(){
return(
<Router>
<div>
<h1>编程式导航</h1>
<Link to="/login">去登录</Link>
<Route path="/login" component={Login}></Route>
<Route path="/home" component={Home}></Route>
</div>
</Router>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
5. 默认路由
● 问题:现在的路由都是点击导航菜单后展示的,如何在进入页面的时候就展示呢?
● 默认路由:表示进入页面时就会匹配的路由
● 默认路由path为: /
<Route path="/" component={Home} />
6. 匹配模式
6.1 模糊匹配
● 问题:当Link组件的to属性值为"/login"
时,为什么默认路由也被匹配成功?
● 默认情况下, React路由是模糊匹配模式
● 模糊匹配规则:只要pathname以path开头就会匹配成功
<Link to="/login">登录页面</Link>
<Route path="/" component={Home} />匹配成功
● path代表Route组件的path属性
● pathname代表Link组件的to属性(也就是location.pathname)
6.2 精确匹配
● 问题:默认路由任何情况下都会展示,如何避免这种问题?
● 给Route组件添加exact属性,让其变为精确匹配模式
● 精确匹配:只有当path和pathname完全匹配时才会展示该路由
// 此时,该组件只能匹配pathname="/" 这一种情况
<Route exact path="/" component=. . . />
推荐: 给默认路由添加exact属性。
7. 总结–React路由基础
-
React路由可以有效的管理多个视图(组件)实现SPA
-
Router组件包裹整个应用 ,只需要使用- -次
-
Link组件是入口 , Route组件是出口
-
通过props.history实现编程式导航
-
默认模糊匹配,添加exact变精确匹配
-
React路由的一切都是组件,可以像思考组件一样思考路由