1. 什么是React
React是一个用于构建用户界面的JavaScript库。用户界面:HTML页面(前端)
React主要用来写HTML页面,或构建Web应用。
如果从MVC的角度来看,React仅仅是视图层(V),也就是只负责视图的渲染,而并非提供了完整的M和C的功能。
2. React的特点
1. 声明式
我们只需要描述UI(HTML)看起来是什么样,就跟写HTML一样。
React负责渲染UI,并在数据变化时更新UI。
const jsx= <div className=”app”>
<h1>Hellp React!动态数据变化:{count}</h1>
</div>
2. 基于组件
组件表示页面中的部分内容,组合,复用多个组件,可以实现完整的页面功能。
3. 学习一次,随时使用
使用React可以开发Web应用
使用React可以开发移动端原生应用(react-native)
使用React可以开发VR应用(react360)
3. React的基本使用
1. React的安装
npm i react react-dom
react包是核心,提供创建元素,组件等功能;react-dom包提供DOM相关功能
3. 引入react和react-dom两个js文件
4. 创建React元素
<script>
//参数一:元素名称
//参数二:元素属性
//参数三及其以后的参数:元素的子节点
const title=React.createElement(‘h1’,null,’Hello React’)
//渲染React元素到页面中
//参数一:要渲染的react元素
//参数二:挂载点
ReactDOM.render(title,document.getElementById(‘root’))
</script>
4. React脚手架的使用
1. 脚手架是开发现代Web应用的必备
现代的Web开发应用已经不再是像传统的那种方式→你只要去创建一个页面,然后在页面中去写HTML,写CSS,写JS,然后最终就把功能实现了。
注意:现代的Web应用已经不再是这样子了。
现在的Web应用,其实写代码只是其中要考虑的一环,我们还要考虑到像代码的规范呀,最终项目打包上线呀,编译工具等等。
我们是需要一套完整的解决方案,来去帮我们开发项目的。
2. 充分利用Webpack,Babel,ESLint等工具辅助项目开发
3. 零配置,无需手动配置繁琐的工具即可使用。
初始化命令
npx create-react-app my-app
启动项目,在项目根目录执行命令:
npm start
5. npx命令
npm v5.2.0引入的一条命令。
目的:提升包内提供的命令行工具的使用体验
原来:先安装脚手架包,再使用这个包中提供的命令
现在:无需安装脚手架包,就可以直接使用这个包提供的命令。
推荐使用:npx create-react-app my-app
npm init react-app my-app
yarn create react-app my-app
在脚手架中使用React
1. 导入react和react-dom两个包
(不再通过script标签导入了,而是通过ES6里面的模块化语法导入)
import React from ‘react’
import ReactDOM from ‘react-dom’
2. 调用React.createElement()方法创建react元素
3. 调用ReactDOM.render()方法渲染react元素到页面中
6. JSX
1. createElement()的缺点
繁琐不简洁,不直观,无法一眼看出所描述的结构
JSX是JavaScript XML的简写,表示在JavaScript代码中写XML(HTML)格式的代码。
优势:声明式语法更加直观,与HTML结构相同,降低了学习成本,提升开发效率。
2. JSX的使用步骤
1. 使用JSX语法创建react元素
const title=<h1>Hello JSX</h1>
2. 使用ReactDOM.render()方法渲染react元素到页面中
ReactDOM.render(title,root)
为什么脚手架中可以使用JSX语法
1. JSX不是标准的ECMAScript语法,它是ECMAScript的扩展语法。
(默认情况下,在一个普通的HTML页面中去写JSX语法,是会报错的)
2. 需要使用babel编译处理后,才能在浏览器环境中使用
3. create-react-app脚手架中已经默认有该配置,无需手动配置。
4. 编译JSX语法的包为:@babel/preset-react
3. JSX使用注意点
1. React元素的属性名使用驼峰命名法
2. 特殊属性名:class -->className,for ->htmlFor,tabindex -->tabIndex
3. 没有子节点的React元素可以使用/>结束
4. 推荐:使用小括号包裹JSX,从而避免JS中的自动插入分号陷阱
//使用小括号包裹JSX
const div=(
<div>Hello JSX</div>
)
4. 在JSX中使用JavaScript表达式
为什么要嵌入JS表达式
数据存储在JS中
语法:{JavaScript表达式}
注意:语法中是单大括号,不是双大括号!
const name=’Jack’
const dv=(
<div>你好,我叫:???</div>
)
const name=’Jack’
const dv=(
<div>你好,我叫:{name}</div>
)
JSX中使用JavaScript表达式的注意点
1. 单大括号中可以使用任意的JavaScript表达式。
2. JSX自身也是JS表达式
const h1=<h1>我是JSX</h1>
const dv={
<div>嵌入表达式:{h1}</div>
}
1. 注意:JS中的对象是一个例外,一般只会出现在style属性中
2. 注意:不能在{}中出现语句(比如:if/for等)
5. JSX的条件渲染
const isloading = true
const loadData=()=>{
if(isLoading){
return <div> loading...</div>
}
return <div>数据加载完成,此处显示加载后的数据</div>
}
const title=(
<h1>
条件渲染:{loadData()}
</h1>
)
6. JSX的列表渲染
如果要渲染一组数据,应该使用数组的map()方法。
注意:渲染列表时应该添加key属性,key属性的值要保证唯一
原则:map()遍历谁,就给谁添加key属性。
注意:尽量避免使用索引号作为key
const songs=[
{id:1,name:’痴心绝对’},
{id:2,name:’像我这样的人’},
{id:3,name:’南山南’},
]
const list=(
<ul>
{songs.map(item=><li key={item.id}>{item.name})</li>
</ul>
)
7. JSX的样式处理
1. 行内样式——style
<h1 style={{ color:’red’,backgroundColor:’skyblue’ }}>
JSX的样式处理
</h1>
2. 类名——className(推荐)
<h1 className=”title”>
JSX的样式处理
</h1>
8. JSX是React的核心内容
JSX表示在JS代码中写HTML结构,是React声明式的体现
使用JSX配合嵌入的JS表达式,条件渲染,列表渲染,可以描述任意UI结构
推荐使用className的方式给JSX添加样式。
React完全利用JS语言自身的能力来编写UI,而不是造轮子增强HTML功能。
7. React组件基础
使用React就是在使用组件。组件表示页面中的部分功能,组合多个组件实现完整的页面功能。
组件的特点:可复用,独立,可组合。
1. React组件的两种创建方式
1. 使用函数创建组件
函数组件:使用JS的函数(或箭头函数)创建的组件。
约定1:函数名称必须以大写字母开头
约定2:函数组件必须有返回值,表示该组件的结构
如果返回值为null,表示不渲染任何内容
function Hello() {
return (
<div>这是我的第一个函数组件!</div>
)
}
// 渲染函数组件:用函数名作为标签名
ReactDOM.render(<Hello/>,root)
2. 使用类创建组件
类组件:使用ES6的class创建的组件
约定1:类名称也必须以大写字母开头
约定2:类组件应该继承ReactCompinent父类,从而可以使用父类中提供的方法或属性。
约定3:类组件必须提供render()方法
约定4:render()方法必须有返回值,表示该组件的结构
class Hello extends React.Component {
render(){
return <div>Hello Class Component!</div>
}
}
ReactDOM.render(<Hello />,root)
2. 抽离为独立JS文件
项目中的组件多了之后,该如何组织这些组件呢?
组件作为一个独立的个体,一般都会放到一个单独的JS文件中。
1. 创建Hello.js
2. 在Hello.js中导入React //import React from ‘react’
3. 创建组件(函数或类)
4. 在Hello.js中导出该组件 //export default Hello
5. 在index.js中导入Hello组件 //import Hello from ‘./Hello’
6. 渲染组件 //ReactDOM.render(<Hello/>,root)
3. React事件处理
1. 事件绑定
React事件绑定语法与DOM事件语法相似
语法:on+事件名称={事件处理程序},
例如:onClick={()=>{}}
注意:React事件采用驼峰命名法:比如:onMouseEnter,onFocus
在类组件中绑定事件
class App extends React.Component{
handleClick(){
console.log(‘单击事件触发了’)
}
render(){
return(
<button onClick={this.handleClick}></button>
)
}
}
在函数组件中绑定事件:
function App() {
function handleClick() {
console.log(‘单击事件触发了’)
}
return(
<button onClick={handleClick}>点我</button>
)
}
2. 事件对象
可以通过事件处理程序的参数获得到事件对象。
React中的事件对象叫做:合成事件(对象)
合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题。
function handleClick(e){
e.preventDefault()
console.log(‘事件对象’,e)
}
<a onClick={handleClick>点我,不会跳转页面</a>
4. 有状态组件和无状态组件
函数组件又叫做无状态组件,类组件有叫做有状态组件
状态(state)即数据
函数组件没有自己的状态,只负责数据展示(静)
类组件有自己的状态,负责更新UI,让页面“动”起来。
比如计数器案例中,点击按钮让数值加1。0和1就是不同时刻的状态,而由0变为1就表示状态发生了变化。
状态变化后,UI也要相应的更新。React中想要实现该功能,就要使用有状态组件来完成。
5. 组件中的state和setState
状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
1. 通过this.state来获取状态
state的值是对象,表示一个组件中可以有多个数据。
class Hello extends React.Component{
constructor(){
super() //ES6规则
//初始化state
this.state={
count:0
}
}
render(){
return(
<div>有状态组件</div>
)
}
}
class Hello extends React.Component{
//简化语法
state={
count:0
}
render(){
return(
<div>有状态组件,{this.state.count}</div>
)
}
}
2. setState()修改状态
状态是可变的
语法:this.setState({要修改的数据})
注意:不要直接修改state中的值,这是错误的!!!
setState()作用:1.修改state 2. 更新UI
思想:数据驱动视图
//正确
this.setState({
count:this.state.count+1
})
//错误
this.state.count +=1
6. 事件绑定this指向
1. 箭头函数
利用箭头函数自身不绑定this的特点
class Hello extends React.Component{
onIncrement(){
this.setState({...})
}
render(){
//箭头函数中的this指向外部环境,此处为:render()方法
return(
<button onclick={()=>this.onIncrement()}></button>
)
}
}
2. Function.prototype.bind()
利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起。
class Hello extends React.Component{
constructor(){
super()
this.state={
count:0
}
this.onIncrement=this.onIncrement.bind(this)
}
//事件处理程序
onIncrement(){
this.setState({
count:this.state.count+1
})
}
render(){
//箭头函数中的this指向外部环境,此处为:render()方法
return(
<div>
<h1>计数器:{this.state.count}</h1>
<button onclick={this.onIncrement}>+1</button>
</div>
)
}
}
3. class实例方法
利用箭头函数形式的class实例方法
注意:该语法是实验性语法,但是,由于babel的存在可以直接使用。
class Hello extends React.Component{
state={
count:0
}
//事件处理程序(把方法自身改造成箭头函数)
onIncrement=()=>{
this.setState({
count:this.state.count+1
})
}
render(){
//箭头函数中的this指向外部环境,此处为:render()方法
return(
<button onclick={this.onIncrement}>+1</button>
)
}
}
7. 表单处理
1. 受控组件
HTML中的表单元素是可输入的,也就是有自己的可变状态。
而,React中可变状态通常保存在state中,并且只能在setState()方法来修改。
React将state与表单元素值value绑定到一起,由state的值来控制表单元素的值。
受控组件:其值受到React控制的表单元素。
<input type=”text” value={this.state.txt} />
步骤:
1. 在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
2. 给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)
class App extends React.Component{
state={
txt:’’
}
handleChange=e=>{
this.setState({
txt:e.target.value
})
}
render(){
return(
<div>
<input type=”text” value={this.state.txt}
onChange={this.handleChange} />
</div>
)
}
}
文本框,富文本框,下拉框操作value属性
复选框操作checked属性
多表单元素优化:使用一个事件处理程序同时处理多个表单元素。
1. 给表单元素添加nama属性,名称与state相同
2. 根据表单元素类型获取对应值
3. 在change事件处理程序中通过[name]来修改对应的state
2. 非受控组件(DOM方式)
借助于ref,使用原生DOM方式来获取表单元素值
使用步骤:
1. 调用React.createRef()方法创建一个ref对象
2. 将创建好的ref对象添加到文本框中
3. 通过ref对象获取到文本框的值
class App extends React.Component{
constructor(){
super()
//创建ref
this.txtRef=React.createRef()
}
//获取文本框的值
getTxt=()=>{
console.log(‘文本框值为:’,this.txtRef.current.value);
}
render(){
return(
<div>
<input type=”text” ref={this.txtRef} />
<button onClick={this.getTxt}>获取文本框的值</button>
</div>
)
}
}
8. React组件通讯
组件是独立且封闭的单元,默认情况下,只能使用自己的数据。
而多个组件之间不可避免的要共享某些数据。为了实现这一点,就需要打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯。
1. 通过props实现
组件是封闭的,要接收外部数据应该通过props实现。
props的作用:接收传递给组件的数据。
传递数据:给组件标签添加属性。
<Hello name=”jack” age={19} />
接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
function Hello(props){
return(
<div>接收到数据:{props.name}</div>
)
}
2. props传递数据的特点
1. 可以给组件传递任意类型的数据
2. props是只读的对象,只能读取属性的值,无法修改对象。
3. 注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props。
class Hello extends React.Component{
constructor(props){
//推荐将props传递给父类构造函数
super(props)
}
render(){
return <div>接收到的数据:{this.props.age}</div>
}
}
3. 组件通讯的三种方式
1. 父组件→子组件
1.1 父组件提供要传递的state中的数据
1.2 给子组件标签添加属性,值为state中的数据
1.3 子组件中通过props接收父组件中传递的数据
2. 子组件→父组件
1. 父组件提供一个回调函数(用于接收数据)
2. 将该函数作为属性的值,传递给子组件
3. 子组件通过props调用回调函数
4. 将子组件的数据作为参数传递给回调函数
3. 兄弟组件
将共享状态提升到最近得公共父组件中,由公共父组件管理这个状态。
公共父组件职责:1. 提供共享状态;2. 提供操作共享状态得方法
要通讯的子组件只需通过props接收状态或操作状态的方法。
4. 跨组件传递数据Context
作用:跨组件传递数据
使用步骤:
1. 调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件。
const {Provider,Consumer}=React.createContext()
2. 使用Provider组件作为父节点
<Provider>
<div className=”App”>
<Childl />
</div>
</Provider>
3. 设置value属性,表示要传递的数据
<Provider value=”pink”>
4. 调用Consumer组件接收数据
<Consumer>
<data =><span>data参数表示接收到的数据--{data}</span>
</Consumer>
5. props深入
children属性:表示组件标签的子节点,当组件有子节点时,props就会有该属性。
children属性与普通的props一样,值可以是任意值(文本,React元素,组件,甚至是函数)
6. props校验
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据。
如果传入的数据格式不对,可能会导致组件内部报错。
1. 使用props校验的原因
关键问题:组件的使用者不知道明确的错误原因。
props校验:允许在创建组件的时候,就指定props的类型,格式等。
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增强组件的健壮性。
App.proTypes={
colors:PropTypes.array
}
2. 使用步骤
1. 安装包prop-types(npm install prop-types)
2. 导入prop-types包(import ProTypes from ‘prop-types’)
3. 使用组件名.propTypes={}给组件的props添加校验规则
4. 校验规则通过PropTypes对象来指定(例如PropTypes.array)
3. 约束规则
1. 常见类型:array,bool,func,number,object,string
2. React元素类型:element
3. 必填项:isRequired
4. 特定结构的对象:shape({})
//常见类型
optionalFunc:PropTypes.func,
//必选
requiredFunc:PropTypes.func.isRequired,
//特定结构的对象
optionalObjectWithShape:PropTypes.shape({
color:PropTypes.string,
fontSize:PropTypes.number
})
4. props的默认值
场景:分页组件——》每页显示条数
function App(props){
return (
<div>
此处展示props的默认值:{props.pageSize}
</div>
)
}
//设置默认值
App.defaultProps={
pageSize:10
}
//不传入pageSize属性
<App />
9. 组件的生命周期
组件的生命周期,有助于理解组件的运行方式,完成更复杂的组件功能,分析组件错误原因等。
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程。
生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数。
钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。
只有类组件才有生命周期。
1. 生命周期的三个阶段
创建时→更新时→卸载时
1. 创建时(挂载阶段)
执行时机:组件创建时(页面加载时)
执行顺序:
constructor()→render()→componentDidMount()
钩子函数 | 触发时机 | 作用 |
constructor | 创建组件时,最先执行 | 初始化state 为事件处理程序绑定this |
render | 每次组件渲染都会触发 | 渲染UI 不能调用setState() |
componentDidMount | 组件挂载(完成DOM渲染)后 | 发送网络请求 DOM操作 |
2. 更新时(更新阶段)
执行时机:1. setState() 2. forceUpdate() 3. 组件接收到新的props
说明:以上三者任意一种变化,组件就会重新渲染。
执行顺序:
render()→componentDidUpdate()
钩子函数 | 触发时机 | 作用 |
render | 每次组件渲染都会触发 | 渲染UI 与挂载阶段是同一个render |
componentDidUpdate | 组件更新(完成DOM渲染)后 | 发送网络请求 DOM操作 如果要setState()必须放在一个if条件中 |
3. 卸载时(卸载阶段)
执行时机:组件从页面中消失
钩子函数 | 触发时机 | 作用 |
componentWillUnmount | 组件卸载 | 执行清理工作 比如清理定时器 |
2. React组件复用
复用的是:1. state 2. 操作state的方法
两种方式:1. render props模式 2. 高阶组件(HOC)
注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)。
1. render props模式
思路:将要复用的state和操作state的方法封装到一个组件中。
问题1:如何拿到该组件中复用的state?
在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)
<Mouse render={ (mouse)=>{} } />
问题2:如何渲染任意的UI?
使用该函数的返回值作为要渲染的UI内容。(需要组件内部实现)
<Mouse render={ (mouse)=>(<p>鼠标当前位置{mouse.x},{mouse.y}</p>) }
children代替render属性
注意:并不是该模式叫render props就必须使用名为render的prop,实际上可以使用任意名称的prop。
把prop是一个函数并且告诉组件要渲染什么内容的技术叫做:render props模式。
推荐:使用children代替render属性。
<Mouse>
{ ({x,y})=><p>鼠标的位置是{x},{y}</p>}
</Mouse>
//组件内部:
this.props.children(this.state)
代码优化
// 推荐:给render props模式添加props校验
Mouse.propTypes={
children:PropTypes.func.isRequired
}
// 应该在组件卸载时解除mousemove事件绑定
componentWillUnmount(){
window.removeEventListener(‘mouse’,this.handleMouseMove)
}
2. 高阶组件
目的:实现状态逻辑复用,采用包装模式。
高阶组件就相当于手机壳,通过包装组件,增强组件功能。
高阶组件(HOC)是一个函数,接收要包装的组件,返回增强后的组件。
const EnhancedComponent=withHOC(WrappedComponent)
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件WrappedComponent
使用步骤:
1. 创建一个函数,名称约定以with开头
2. 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
3. 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
function withMouse(WrappedComponent){
class Mouse extends React.Component{}
return Mouse
}
4. 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
//Mouse组件的render方法中:
return <WrappedComponent {...this.state} />
5. 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中。
//创建组件
const MousePosition=withMouse(Position)
//渲染组件
<MousePosition />
3. 设置displayName
使用高阶组件存在的问题:得到的两个组件名称相同。
原因:默认情况下,React使用组件名称作为displayName
解决方式:为高阶组件设置displayName便于调试时区分不同的组件。
displayName的作用:用于设置调试信息(ReactDeveloper Tools信息)
Mouse.displayName=`WithMouse${getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName||WrappedComponent.name||’Component’
}
渲染WrappedComponent时,将state和this.props一起传递给组件
<WrappedComponent {...this.state} {...this.props} />
10. React原理
1. setState()的说明
setState()是异步更新数据的
this.state={count:1}
this.setState({
count:this.state.count+1
})
//此处打印出来的是1
console.log(this.state.count)
注意:使用该语法时,后面的setState()不要依赖于前面的setState()
可以多次调用setState(),只会触发一次重新渲染。
推荐语法:使用setState((state,props)=>{})语法
注意这种语法也是异步更新state的!
参数state:表示最新的state
参数props:表示最新的props
this.setState((state,props)=>{
return {
count:state.count+1
}
})
console.log(this.state.count)
第二个参数:
场景:在状态更新(页面完成重新渲染)后立即执行某个操作
语法:setState(updater,[callback])
this.setState(
(state,props)=>{},
()=>{console.log(‘这个回调函数会在状态更新后立即执行’)}
)
2. JSX语法的转化过程
JSX仅仅是createElement()方法的语法糖(简化语法)
JSX语法:
const element=(
<h1 className=”greeting”>
Hello JSX!
</h1>
)
createElement()语法
const element=React.createElement(‘h1’,{className:’greeting’},‘Hello JSX!’);
JSX语法被@babel/preset-react插件编译为createElement()方法
React.createElement又在内部被转化为了React元素
React元素:是一个对象,用来描述你希望在屏幕上看到的内容。
3. 组件更新机制
setState()的两个作用:1. 修改state 2. 更新组件(UI)
过程:父组件重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树(当前组件及其所有子组件)
4. 组件性能优化
1. 减轻state:只存储跟组件渲染相关的数据(比如:count/列表数据/loading等)
注意:不用做渲染的数据不要放在state中,比如定时器id等
对于这种需要在多个方法中用到的数据,应该放在this中。
class Hello extends Component{
componentDidMount(){
//timerId存储到this中,而不是state中
this.timerId=setInterval(()=>{},2000)
}
componentWillUnmount(){
clearInterval(this.timerId)
}
render(){...}
}
2. 避免不必要的重新渲染
组件更新机制:父组件更新会引起子组件也被更新。
问题:子组件没有任何变化时也会重新渲染。
解决办法:用钩子函数shouldComponentUpdate(nextProps,nextState)
作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,返回false表示不重新渲染。
触发时机:更新阶段的钩子函数,组件重新渲染前执行
(shouldComponentUpdate→render)
shouldComponentUpdate(nextProps,nextState){
//最新的state,当前的state
if(nextState.number === this.state.number){
return false
}
return true
}
3. 纯组件
纯组件:PureComponent与React.Component功能相似
class Hello extends React.PureComponent{
render(){
return(
<div>纯组件</div>
)
}
}
区别:PureComponent内部自动实现了shouldComponentUpdate钩子,不需要手动比较。
原理:纯组件内部通过分别对比前后两次props和state的值,来决定是否重新渲染组件。
说明:纯组件内部的对比是shallow compare(浅层对比)
对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)
state={ number:0 }
setState({
number:Math.floor(Math.random()*3)
})
//PureComponent内部对比:
// 最新的state.number === 上一次的state.number //false,重新渲染
对于引用类型来说:只比较对象的引用(地址)是否相同
const obj={number:0}
const newObj=obj
newObj.number=2
console.log(newObj === obj) //true
注意:state或props中属性值为引用类型时,应该创建新数据,不要直接修改原数据。
//正确!创建新数据
const newObj={ ...state.obj,number:2}
setState({obj:newObj})
//正确!创建新数据
//不要用数组的push/unshift等直接修改当前数组的方法
//而应该用concat或slice等这些返回新数组的方法
this.setState({
list:[...this.state.list,{新数据}]
})
5. 虚拟DOM和Diff算法
React更新视图的思想是:只要state变化就重新渲染视图。
特点:思路非常清晰。
问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?
理想状态:部分更新,只更新变化的地方。
React就是通过虚拟DOM配合Diff算法做到部分更新的。
1. 什么是虚拟DOM
本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)。
虚拟DOM对象
const element={
type:’h1’,
props:{
className:’greeting’,
children:’Hello JSX!’
}
}
HTML结构
<h1 class=”greeting”>
Hello JSX!
</h1>
2. 执行过程
1. 初次渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(树)
2. 根据虚拟DOM生成真正的DOM渲染到页面中。
3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)
4. 与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容。
5. 最终,React只将变化的内容更新(patch)到DOM中,重新渲染页面。
组件render()调用之后,根据状态和JSX结构生成虚拟DOM对象。
render方法调用并不意味着浏览器中的重新渲染!!!
render方法调用仅仅说明要进行diff。
虚拟DOM的真正价值从来都不是性能,它让React脱离了浏览器环境的束缚。
11. React路由基础
现代的前端应用大多都是SPA(单页面应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好,对服务器的压力更小,所以更受欢迎。
为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生。
前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
前端路由是一套映射规则,在React中,是URL路径与组件的对应关系。
使用React路由简单来说,就是配置路径和组件(配对)。
1. 使用步骤
1. 安装npm instasll react-router-dom
2. 导入路由的三个核心组件:Router/Route/Link
import {BrowserRouter as Router, Route, Link } from ‘react-router-dom’
3. 使用Router组件包裹整个应用(重要)
<Router>
<div className=”App”>
//...省略页面内容
</div>
</Router>
4. 使用Link组件作为导航菜单(路由入口)
<Link to=”/first”>页面一</Link>
5. 使用Route组件配置路由规则和要展示的组件(路由出口)
2. 两种常用Router
HashRouter和BrowserRouter
HashRouter:使用URL的哈希值实现(localhost:3000/#/first)
BrowserRouter:使用H5的history API实现(localhost:3000/first)
Link组件:用于指定导航链接(最终都会被编译成a标签)
to属性:浏览器地址栏中的pathname(location.pathname)
Route组件:指定路由展示组件相关信息
//path属性:路由规则
//component属性:展示的组件
//Route组件写在哪里,渲染出来的组件就在哪里
<Route path=”/first” component={First}></Route>
3. 路由的执行过程
1. 点击Link组件(a标签),修改了浏览器地址栏中的url
2. React路由监听到地址栏URL的变化。
3. React路由内部遍历所有Route组件,使用路由规则(path)与pathname进行配对。
4. 当路由规则path能够匹配地址栏中的pathname时,就展示该Route组件的内容。
4. 编程式导航
通过JS代码来实现页面跳转
history是React路由提供的,参数path表示要跳转的路径
push(path):跳转到某个页面,参数path表示要跳转的路径
go(n):前进或后退
class Login extends Component {
handleLogin=()=>{
//...
this.props.history.push(‘/home’)
}
render(){...省略其他代码}
}
5. 默认路由
表示进入页面时就会匹配的路由,path为:/
<Route path=”/” component={Home} />
6. 匹配模式
1. 模糊匹配模式
默认情况下,React路由是模糊匹配模式
模糊匹配规则:只要pathname以path开头就会匹配成功
// 如上述,/login以/开头。
// pathname代表Link组件的to属性
<Link to=”/login”>登录页面</Link>
// path代表Route组件的path属性
<Route path=”/” component={Home} />匹配成功
2. 精确匹配模式
给Route组件添加exact属性,让其变为精确匹配模式。
精确匹配:只有当path和pathname完全匹配时才会展示该路由
//此时,该组件只能匹配pathname=”/”这一种情况
<Route exact path=”/” component=.../>