视频教程:黑马程序员前端React视频教程,react零基础入门原理详解到好客租房项目实战_哔哩哔哩_bilibili
笔记:
React是一个用于构建用户界面的JavaScript库
用户界面:HTML页面
React主要用来写HTML页面,或构建Web应用
如果从MVC的角度来看,React仅仅是视图层(V),也就是只负责视图的渲染,而并非提供了完整的M和C的功能。
React起源于Facebook的内部项目,后又用来架设Instagram的网站,并于2013年5月开源
React的特点:
声明式
你只需要描述UI看起来是什么样,就跟写HTML一样
基于组件
组件是React最重要的内容
组件表示页面中的部分内容
学习一次,随处使用
使用React可以开发web应用
使用React可以开发移动端原生应用(react-native)
使用React可以开发VR(虚拟现实)应用(react360)
React的安装
安装命令:npm i react react-dom
react包是核心,提供创建元素、组件等功能
react-dom包提供DOM相关功能等
React的使用
<body>
1.引入react和react-dom两个js文件
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<div id="root"></div>
<script>
// 2.创建React元素
// 三个参数分别为:元素名称、元素属性、元素子节点
const title = React.createElement('h1',null,'Hello React')
3.渲染React元素到页面中
// 两个参数分别为:要渲染的react元素、挂载点
ReactDOM.render(title,document.getElementById('root'))
</script>
</body>
React脚手架意义
1.脚手架是开发现代web应用的必备
2.充分利用Webpack、Babel、ESLint等工具辅助项目开发
3.零配置,无需手动配置繁琐的工具即可使用
4.关注业务,而不是工具配置
使用React脚手架初始化项目
1.初始化项目,命令:推荐:npx create-react-app 项目名称
也可以使用:npm init react-app 项目名称
或:yarn create react-app 项目名称
2.启动项目,在项目根目录命令:npm start(路径中不能有中文)
npx命令介绍
npm v5.2.0引入的一条命令
目的:提升包内提供的命令行工具的使用体验
原来:先安装脚手架包,再使用这个包中提供的命令
现在:无需安装脚手架包,就可以直接使用这个包提供的命令
在脚手架中使用React
1.导入react和react-dom两个包
import React from 'react'
import ReactDOM from 'react-dom'
2.调用React.createElement()方法创建reate元素。
3.调用ReactDOM.render()方法渲染reate元素到页面中
createElement()的问题
1.繁琐不简洁
2.不直观,无法一看看出所描述的结构
3.不优雅,用户体验不爽
JSX简介
JSX是JavaScript XML的简写,表示在JavaScript代码中写XML(HTML)格式的代码
优势:声明式语法更加直观,与HTML结构相同,降低了学习成本、提升开发效率
JSX是React的核心内容
使用步骤
1.使用JSX语法,创建react元素
const title = <h1>Hello JSX</h1>
2.使用ReactDOM.render()方法渲染react元素到页面中
ReactDOM.render(title,document.getElementById('root'))
为什么脚手架中可以使用JSX语法?
1.JSX不是标准的ECMAScript语法,它是ECMAScript的语法扩展
2.需要使用babel编译处理器后,才能在浏览器环境中使用
3.create-react-app脚手架中已经默认有该配置,无需手动配置
4.编译JSX语法的包为:@babel/preset-react
JSX的注意点
1.React元素的属性名使用驼峰命名法
2.特殊属性名:class->className、for->htmlFor、tabindex->tabIndex
3.没有子节点的React元素可以用/>结束
4.推荐:使用小括号包裹JSX,从而避免JS中的自动插入分号陷阱
const dv = (
<div>Hello JSX</div>
)
嵌入JS表达式
数据存储在JS中
语法:{JavaScript表达式}
注意:语法中是但大括号,不是双大括号
const name = 'Jack'
const dv = (
<div>你好,我叫:{name} </div>
)
JSX中使用JavaScript表达式
注意点
单大括号中可以使用任意的JavaScript表达式
JSX自身也是JS表达式
注意:jS中的对象是一个例外,一般只会style属性中
注意:不能在{}中出现语句(比如:if、for等)
JSX的条件渲染
场景:loading效果
条件渲染:根据条件渲染特定的JSX后结构
可以使用if/else或三元运算符或逻辑与运算符来实现
const isLoading = false
// if-else
const loadData = ()=>{
if (isLoading){
return <div>loading ...</div>
}
return <div>数据加载完成,此处显示加载之后的数据</div>
}
// 三元表达式
const loadData = ()=>{
return isLoading?( <div>loading ...</div>):( <div>数据加载完成,此处显示加载之后的数据</div>)
}
//逻辑与运算符
const loadData = ()=>{
return isLoading &&( <div>loading ...</div>)}
const title =(
<h1>
条件渲染:
{loadData()}
</h1>
)
ReactDOM.render(title,document.getElementById('root'))
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>
)
JSX的样式处理
1.行内样式——style
<h1 style={
{color:'red',backgroundColor:'skyblue'}}>
JSX的样式处理
</h1>
2.类名——className(推荐)
import './css/index.css'
<h1 className="title"}>
JSX的样式处理
</h1>
index.css文件里:
.title{
text-align:center;
}
JSX总结
1.JSX是React的核心内容。
2.JSX表示在JS代码中写HTML结构,是React声明式的体现
3.使用JSX配合嵌入的JS表达式,条件渲染,列表渲染,可以描述任意UI结构
4.推荐使用className的方式给JSX添加样式
5.React完全使用JS语言自身的能力来编写UI,而不是造轮子增强HTML功能
React组件介绍
组件是React的一等公民,使用React就是在用组件
组件表示页面中的部分功能
组合多个组件实现完整的页面功能
特点:可复用,独立,可组合
React组件的两种创建方式
1.使用函数创建组件
函数组件:使用JS的函数(或箭头函数)创建的组件
约定1:函数名称必须以大写字母开头,React据此区分组件和普通的React元素
约定2:函数名称必须有返回值,表示该组件的结构
如果返回值为null,表示不渲染任何内容
渲染函数组件:用函数名作为组件标签名
组件标签可以是单标签也可以是双标签
function Hello(){
return(
<div>这是我的第一个函数组件!</div>
)
}
或
const Hello=()=><div>这是我的第一个函数组件!</div>
ReactDOM.render(<Hello/>,document.getElementById('root'))
2.使用类创建组件
类组件:使用ES6的class创建的组件
约定1:类名称必须以大写字母开头
约定2:类组件应该继承React.Component父类,从而可以使用父类中提供的方法或属性
约定3:类组件必须提供render()方法
约定4:render()方法必须有返回值,表示该组件的结构
如果返回值为null,表示不渲染任何内容
class Hello extends React.Component{
render(){
return <div>这是我的第一个类组件!!</div>
}
}ReactDOM.render(<Hello/>,document.getElementById('root'))
把组件抽离为独立JS文件
1.创建Hello.js
2.在Hello.js中导入React
3.创建组件(函数或类)
4.在Hello.js中导出该组件
5.在index.js中导入Hello组件
// Hello.js
import React from 'react'
class Hello extends React.Component{
render(){
return <div>这是我的第一个类组件!!</div>
}
}
// 导出Hello组件
export default Hello
//inedx.js
import Hello from '/.Hello'
//渲染导入的Hello组件
ReactDOM.render(<Hello/>,document.getElementById('root'))
事件绑定
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>
)
}
事件对象
可以通过事件处理程序的参数获取到事件对象
React中的事件对象叫做:合成事件(对象)
合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
function handleClick(e){
// 组织浏览器的默认行为
e.preventDefault()
console.log('事件对象',e)
}
<a onClick={handleClick}>点我,不会跳转页面</a>
有状态组件和无状态组件
函数组件又叫做无状态组件,类组件又叫做有状态组件
状态(state)即数据
函数组件没有自己的状态,只负责数据展示(静)
类组件有自己的状态,负责更新UI,让页面动起来
组件中的state和setState
state的基本使用
状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
state的值是对象,表示一个组件中可以有多个数据
获取状态:this.state
状态是可变的
语法:this.setState({要修改的数据})
setState()作用:1.修改state2.更新UI
思想:数据驱动视图
class Hello extends React.Component{
constructor(){
// 原本语法:
super()
// 初始化state
this.state={
count:0
}
//简化语法(推荐 )
state={
count:0
}
render(){
return(
<div>计数器:{this.state.count}</div>
<button onClick={()=>{
this.setState({
count:this.state.count+1
})
}}>+1</button>
</div>
)
}
}
从JSX中抽离事件处理程序
JSX中掺杂过多JS逻辑代码,会显得非常混乱
推荐:将逻辑抽离到单独的方法中,保证JSX逻辑清晰
class Hello extends React.Component{
constructor(){
//简化语法(推荐 )
state={
count:0
}
onIncrement(){
this.setState({
count:this.state.count+1
})
}
render(){
return(
<div>计数器,{this.state.count}</div>
<button onClick={()=>this.onIncrement}>+1</button>
</div>
)
}
}
事件绑定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.onIncrement=this.onIncrement.bind(this)
}
//…省略onIncrement
render(){
return(
<button onClick={this.onIncrement}></button>
)
}
}
3.class的实例方式(推荐)
利用箭头函数形式的class实例方法
注意:该语法是实验性语法,但是,由于babel的存在可以直接使用
class Hello extends React.Component{
onIncrement=()=>{
this.setState({……})
render(){
return(
<button onClick={this.onIncrement}></button>
)
}
}
表单处理
受控组件
HTML 中的表单元素是可输入的,也就是有自己的可变状态
而,React中可变状态通常保存在state中,并且只能通过setState()方法来修改
React将state 与表单元素值value绑定到一起,由state的值来控制表单元素的值
受控组件∶其值受到 React控制的表单元素
步骤:
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>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
富文本框:
class App extends React.Component{
state={content:''}
handleChange=e=>{
this.setState({
city:e.target.value
})
}
render(){
return(
<div>
<textarea value={this.state.content} onChange={this.handleChange}></textarea>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
下拉框:
class App extends React.Component{
state={city:'bj'}
handleChange=e=>{
this.setState({
content:e.target.value
})
}
render(){
return(
<div>
<select value={this.state.city} onChange={this.handleChange}>
<option value="sh">上海</option>
<option value="bj">北京</option>
<option value="gz">广州</option>
</select>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
复选框:
class App extends React.Component{
state={isChecked:false}
handleChange=e=>{
this.setState({
isChecked:e.target.checked
})
}
render(){
return(
<div>
<input type="checkbox" checked={this.state.isChecked} onChange={this.handleChange}/>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
多表单元素优化步骤:
1.给表单元素添加name属性,名称与state相同
<input
type="text"
name="txt"
value={this.state.txt}
onChange={this.handleForm}
/>
2.根据表单元素类型获取对应值
const value = target.type==='checkbox'
?target.checked
:target.value
this.setState({
[name]:value
})
3.在change事件处理程序中通过[name]来修改对应的state
非受控组件
借助于ref,使用原生DOM方式来获取表单元素值
ref的作用:获取DOM或组件
使用步骤:
1.调用ReacteRef()方法创建一个ref对象
constructor(){
super()
this.txtRef = React.createRef()
}
2.将创建好的ref对象添加到文本框中
<input type="text" ref={this.txtRef}>
3.通过ref对象获取到文本框的值
Console.log(this.txtRef.current.value)
案例:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
class Hello extends React.Component{
state={
pinglun:[],
userName:"",
userContent:""
}
handleChange=e=>{
const {name,value}= e.target
this.setState({
[name]:value,
})
}
handleButton=()=>{
const{userName,userContent,pinglun}=this.state
const newContent=[{
id:this.state.pinglun.length+1,
Commentator:userName,
content:userContent},...pinglun]
if ( userName===""||userContent==="") {
alert("请输入评论人姓名及评论内容")
}else{
this.setState({
pinglun:newContent,
userName:"",
userContent:""
}
)}
}
render(){
return <div className='border'>
<input className="input" value={this.state.userName} name="userName" placeholder="请输入评论人" onChange={this.handleChange}/>
<textarea className="textarea" name="userContent" value={this.state.userContent} placeholder="请输入评论内容" onChange={this.handleChange}/>
<button className="button" onClick={this.handleButton}>发表评论</button>
<div >{
this.state.pinglun.length===0?(<div className='pinglun'>暂无评论,快去评论吧~</div> )
🙁 <div></div>)
}
<ul>
{this.state.pinglun.map(item=>(
<li key={item.id}>
<h3>评论人:{item.Commentator}</h3>
<div>评论内容:{item.content}</div>
</li>
))}
</ul>
</div>
</div>
}
}ReactDOM.render(<Hello/>,document.getElementById('root'))
组件通信介绍
组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据,在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好地完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就要打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯。
组件的props
组件是封闭的,要接收外部数据应该通过props来实现
props的作用:接收传递给组件的数据
传递数据:给组件标签添加属性
接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
函数组件:
function Hello(props){
console.log(props)
return(
<div>接收到数据:{props.name}</div>
)
}
<Hello name="jack" age={19}/>
类组件:
class Hello extends React.Component{
render(){
return(
<div>接收到数据:{props.name}</div>
)
}
}
<Hello name="jack" age={19}/>
组件的props
特点
1.可以给组件传递任意类型的数据
2.props是只读的对象,只能读取属性的值,无法修改对象
3.注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props
class Hello extends React.Component{
constructor(props){
super(props)
}
render(){
return<div>接受到的数据:{this.props.age}</div>
}
}
组件通讯的三种方式
1.父组件—>子组件
2.子组件—>父组件
3.兄弟组件
父组件传递数据给子组件
1.父组件提供要传递的state数据
2.给子组件标签添加属性,值为state中的数据
3.子组件中通过props接收父组件中传递的数据
父组件:
class Parent extends React.Component{
state = {lastName:'王'}
render(){
return(
<div>
传递数据给子组件:< Child name={this.state.lastName}/>
</div>
)
}
}
子组件:
function Child(props){
return <div>子组件接收到数据:{prop.name}</div>
}
子组件传递数据给父组件
思路:利用回调函数,父组件提供回调函数,子组件调用,将要传递的数据作为回调函数的参数
1.父组件提供一个回调函数(用于接收数据)
2.将该函数作为属性的值,传递给子组件
class Parent extends React.Component{
getChildMsg = (msg) =>{
console.log('接收到子组件数据',msg)
}
render(){
return(
<div>
子组件:<Child getMsg={this.getChildMsg}/>
</div>
}
}
}
3.子组件通过props调用回调函数
4.将子组件的数据作为参数传递给回调函数
class Child extends React.Component{
state = { childMsg:'React'}
handleClick = () =>{
this.props.getMsg(this.state.childMsg)
}
render(){
return(
<button onClick = {this.handleClick}点我,给父组件传递数据</button>
)
}
}
兄弟组件
将共享状态提升到最近的公共组件中,由公共父组件管理这个状态
思想:状态提升
公共父组件职责:1.提供共享状态2.提供操作共享状态的方法
要通讯的子组件只需通过props接收状态或操作状态的方法
//父组件
class Counter extends React.Component{
// 提供共享状态
state = {
count:0
}
// 提供修改状态的方法
onIncrement=()=>{
this.setState({
count:this.state.count+1
})
}
render(){
return(
<div>
<Child1 count={this.state.count}/>
<Child2 onIncrement={this.onIncrement}/>
</div>
)
}
}
const Child1 = props=>{
return <h1>计数器:{props.count}</h1>
}
const Child2 = ( )=>{
return <button onClick={()=>props.onIncrement()}>+1</button>
}
ReactDOM.render(<Counter />,document.getElementById('root'))
Context
作用:跨组件传递数据
如果两个组件是远房亲戚(比如,嵌套多层)可以使用Context实现组件通讯
Context提供了两个组件:Provider和Consumer
Provider组件:用来提供数据
Consumer组件:用来消费数据
使用步骤:
1.调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件
const{Provider,Consumer}=React.createContext()
2.使用Provider组件作为父节点
3.设置value属性,表示要传递的数据
<Provider value="pink">
<div className="APP">
<Child1 />
</div>
</Provider>
4.调用Consumer组件接收数据
<Consumer>
{data =><span>data参数表示接收到的数据——{data}</span>}
</Consumer>
Children属性
表示组件标签的子节点。当组件标签有子节点时,propa就会有该属性
children属性与普通的props一样,值可以是任意值
function Hello(props){
return(
<div>
组件的子节点:{props.children}
</div>
)
}
<Hello>我是子节点</Hello>
props校验
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据格式不对,可能会导致组件内部报错
关键问题:组件的使用者不知道明确的错误原因
props校验:允许在创建组件的时候,就指定props的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组建的健壮性
使用步骤
1.安装包prop-type(yarm add prop-types/npm i prop-types)
2.导入prop-types包
import PropTypes from 'prop-types'
3.使用组件名.propTypes={}来给组件的props添加校验规则
App.propTypes={
colors:PropTypes.array
}
4.校验规则通过PropTypes对象来指定
约束规则:
1.创建类型:array、bool、func、number、object、string
colors:PropTypes.array
2.React元素类型:element
colors:PropTypes.element
3.必填项:isRequired
colors:PropTypes.isRequired
4.特定结构的对象:shape({})
colors:PropTypes.shape({
first:PropTypes.array,
second:PropTypes.number
})
props的默认值
场景:分页组件=>每页显示条数
作用:给props设置默认值,在未传入props时生效
function App(props){
return(
<div>
此处展示props的默认值:{props.pageSize}
</div>
)
}
// 设置默认值
App.defaultProps={
pageSize:10
}
// 不传入pageSize属性
<App/>
组件的生命周期
组件的生命周期概述
意义:组件的生命周期有助于理解组件的运行方式,完成更复杂的组件功能,分析组件错误原因等
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
钩子函数的作用:为开发人员在不同阶段操作组件提供了时机
只有类组件才有生命周期
生命周期的三个阶段
1.每个阶段的执行时机
2.每个阶段钩子函数的执行顺序
3.每个阶段钩子函数的作用
1.创建时(挂载阶段)
执行时机:组件创建时(页面加载时)
执行顺序:constructor()=>render()=>componentDidMount
钩子函数 |
触发时机 |
作用 |
constructor |
创建组件时,最先执行 |
1.初始化state 2.为事件处理程序绑定this |
render |
每次组件渲染都会触发 |
渲染UI(注意:不能调用setState) |
componentDidMount |
组件挂载(完成DOM渲染)后 |
1.发送网络请求 2.DOM操作 |
2.更新时
执行时机:1.setStare()2.forceUplate()3. 组件接收到新的props
说明:以上三者任意一种变化,组件就会重新渲染
执行顺序:render()=>componentDidMount
钩子函数 |
触发时机 |
作用 |
render |
每次组件渲染都会触发 |
渲染UI(与挂载阶段是同一个render) |
componentDidMount |
组件更新(完成DOM渲染)后 |
1.发送网络请求 2.DOM操作 3.注意:如果要setState() 必须放在一个if条件中 |
3.卸载时(卸载阶段)
执行时机:组件从页面中消失
componentWillUnMount |
组件卸载(从页面中消失) |
执行清理工作(比如:清理定时器等) |
render-props和高阶组件
React组件复用概述
两种方式:1.render prop模式2.高阶组件(HOC)
注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)
render-props模式
思路分析
思路:将要复用的state和操作state的方法封装到一个组件中
问题:如何拿到该组件中复用的state?
在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)
问题:如何去渲染任意的UI?
使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)
<Mouse render={(mouse)=>(
<p>鼠标当前位置{mouse.x},{mouse.y}</p>
)}/>
使用步骤
1.创建Mouse组件,在组件中提供复用的状态逻辑代码(1.状态2.操作状态的方法)
2.将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
3.使用props.render()的返回值作为要渲染的内容
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
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)
}
render(){
return this.props.render(this.state)
}
}
class Hello extends React.Component{
render(){
return(
<div>
<h1>render props模式</h1>
<Mouse
render={mouse=>{
return (<p>鼠标位置:{mouse.x},{mouse.y}</p>)
}}/>
</div>
)
}
}ReactDOM.render(<Hello/>,document.getElementById('root'))
Mouse组件的复用
// 导入图片资源
import img from '...'
<Mouse render={mouse=>{
return <img src={img} alt="猫" style={
{
position:"absolute",
top:mouse.y,
left:mouse.x
}}/>
}}></Mouse>
children代替render属性
注意:并不是该模式叫render props就必须使用名为render的prop,实际上可以使用任意名称的prop
把prop是一个函数并且告诉组件要渲染什么内容的技术叫做:render props模式
推荐:使用children代替render属性
<Mouse>
{mouse=>
renter(
<p>鼠标的位置是{x},{y}</p>
)
}
</Mouse>
//组件内部:
this.props.children(this.state)
//Context中的用法:
<Consumer>
{data=><span>data参数表示接收到的数据——{data}</span>}
</Consumer>
代码优化
1.推荐:给render props模式添加props校验
Mouse.propTypes={
children:PropTypes.func.isRequired
}
2.应该在组件卸载时接触mousemove事件绑定
componentWillUnmount(){
window.removeEventListener('mousemove',this.handleMouseMove)
}
高阶组件
概述
目的:实现状态逻辑复用
采用包装(装饰)模式。比如说:手机壳
手机:获取保护功能
手机壳:提供保护功能
高阶组件(HOC)就相当于手机壳,通过包装组件,增强组件功能
思路分析
高阶组件是一个函数,接收要包装的组件,返回增强后的组件
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件
const EnhancedComponent = withHOC(WrappedComponent)
//高阶组件内部创建的类组件:
class Mouse extends React.Component{
render(){
return <WrappedComponent{…this.state}/>
}
}
使用步骤
1.创建一个函数,名称约定以with开头
2.指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
3.在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
4.在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
5.调用该高阶组件,传入要增强的组件,并将其渲染到页面中去
function withMouse(WrappddComponent){
class Mouse extends React.Component{}
return Mouse
}
Mouse组件的render方法中:
return<WrappedComponent{…this.state}>
// 创建组件
const MousePosition = withMouse(Position)
// 渲染组件
<MousePosition/>
设置displayName
使用高阶组件存在的问题:得到的两个组件名称相同
原因:默认情况下,React使用组件名称作为displayName
解决方式:为高阶组件设置displayName便于调式时区分不同的组件
displayName作用:用于设置调试信息(React Developer Tools信息)
设置方式:
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName||WrappedComponent.name||'Component'
}
传递props
问题:props丢失
原因:高阶组件没有往下传递props
解决方式:渲染WrappedComponent时,将state和this.props一起传递给组件
传递方式:
<WrappedComponent {…this.state}{…this.props}/>
React组件进阶总结
1.组件通讯是构建React应用必不可少的一环。
2.props的灵活性让组件更加强大
3.状态提升是React组件的常用模式
4.组件生命周期有助于理解组件的运行过程
5.钩子函数让开发者可以在特定的时机执行某些功能
6.render props模式和高阶组件都可以实现组件状态逻辑复用
7.组件极简模型:(state,props)=>UI
setState()的说明
更新数据
setState()是异步更新数据的
注意:使用该语法时,后面的setState()不要依赖于前面的setState()
可以多次调用setState(),只会触发一次重新渲染
推荐语法
推荐:使用setState((state,props)=>())
参数state:表示最新的state
参数props:表示最新的props
注意:这种方法也是异步更新
第二个参数
场景:在状态更新(页面完成重新渲染)后立即执行某个操作
语法:setState(updater[,callback])
this.setState(
(state,props)=>{},
()=>{console.log('这个回调函数会在状态更新后立即执行')}
)
JSX语法的转化过程
JSX仅仅是createElement()方法的语法糖(简化语法)
JSX语法被@babel/preset-react插件编译为createElement()方法
createElement()方法又被转化为React元素
React元素:是一个对象,用来描述你希望在屏幕上看到的内容
组件更新机制
setState()的两个作用:1.修改state2.更新组件(UI)
过程:父组件重新渲染,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)
组件性能优化
减轻state:只存储跟组件渲染相关的数据(比如:count/列表数据/loading等)
注意:不用做渲染的数据不要放在state中,比如定时器id等
对于这种需要在多个方法中用到的数据,应该放在this中
class Hello extends Component{
componentDidMount(){
// timerID存储到this中,而不是state中
this.timerId=setInterval(()=>{},2000)
}
componentWillUnmount(){
clearInterval(this.timerId)
}
render(){…}
}
避免不必要的重新渲染
组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
问题:子组件没有任何变化时也会重新渲染
如何避免不必要的重新渲染
解决方式:使用钩子函数shouldComponentUpdate(nextProps,nextState)
作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpate=>render)
class Hello extends Component{
shouldComponentUpate(){
//根据条件,决定是否重新渲染组件
return false
}
render() {…}
}
例子:生成随机数
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
class Hello extends React.Component{
state={
number:0
}
handleClick=()=>{
this.setState(()=>{
return {
number:Math.floor(Math.random()*3)
}
})
}
shouldComponentUpdate(nextProps,nextState){
console.log('最新状态:',nextState,',当前状态: ',this.state)
return nextState.number !== this.state.number
}
render(){
return(
<div>
<h1>随机数:{this.state.number}</h1>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}ReactDOM.render(<Hello/>,document.getElementById('root'))
或
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
class Hello extends React.Component{
state={
number:0
}
handleClick=()=>{
this.setState(()=>{
return {
number:Math.floor(Math.random()*3)
}
})
}
render(){
return(
<div>
<NumberBox number={this.state.number}/>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
class NumberBox extends React.Component{
shouldComponentUpdate(nextProps){
return nextProps.number!==this.props.number
}
render(){
return(<h1>随机数:{this.props.number}</h1>)
}
}
ReactDOM.render(<Hello/>,document.getElementById('root'))
纯组件
PureComponent与React.Component功能相似
区别:PureComponent内部自动实现了shouldComponentUpdate钩子,不需要手动对比
原理:纯组件内部通过分别对比前后两次props和state的值,来决定是否重新渲染组件
class Hello extends React.PureComponent{
render(){
return(
<div>纯组件</div>
)
}
}
说明:纯组件内部的对比是shallowCompare(浅层对比)
对于值类型来说,比较两个值是否县相同(直接赋值接即可,没有坑)
let number = 0
let newNumber = number
newNumber = 2
console.log(number === newNumber) // false
state ={number:0}
setState({
number:Math.floor(Math.random()*3)
})
// PureComponent内部对比:
最新的state.number === 上一次的state.number// false,重新渲染组件
对于引用类型来说:只比较对象的引用(地址)是否相同
注意:state或props中属性值为引用类型时,应该创建新数据,不要直接修改原数据
const obj = { number:0 }
const newObj = obj
newObj.number = 2
console.log(newObj ===obj) //true
state ={obj:{number:0}}
// 错误做法
state.obj,number = 2
setState({obj:state.obj})
// PureComponent内部对比:
最新的state.obj === 上一次的state.obj// true,重新渲染组件
//正确!创建新数据
const newObj = {…state.obj,number:2}
setState({obj:newObj})
//正确!创建新数据
// 不要用数组的push/unshift等直接修改当前数组的方法
// 而应该用const 或slice等这些返回新数组的方法
this.setState({
list:[…this.state.list,{新数据}]
})
虚拟DOM和Diff算法
React更新视图的思想是:只要state变化就重新渲染视图
特点:思路非常清晰
问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?不是
理想状态:部分更新,只更新变化的地方
问题:React是如何做到部分更新的?虚拟DOM和Diff算法
虚拟DOM:本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)
执行过程
1.初次渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(树)
2.根据虚拟DOM生成真正的DOM,渲染到页面中
3.当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)
4.与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容
5.最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面
React 原理揭秘总结
1.工作角度:应用第一,原理第二
2.原理有助于更好地理解React的自身运行机制
3.setState()异步更新数据
4.父组件更新导致子组件更新,纯组件提升性能
5.思路清晰简单为前提,虚拟DOM和Diffb保效率
6.虚拟DOM=>state+JSX
7.虚拟DOM的真正价值从来都不是性能
React路由介绍
现代的前端应用大多数都是SPA(单页应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生
前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
前端路由是一套映射规则,在React中,是URL路径与组件的对应关系
使用React路由简单来说,就是配置路径和组件(配对)
使用步骤
1.安装:yarn add react-router-dom/npm i react-router-dom@5.2.0
2.导入路由的三个核心组件:Router/Route/Link
import{ErowserRouter as Router,Route,Link}from 'react-router dom'
3.使用Router组件包裹整个应用(重要)
<Router>
<div className="APP">
// …省略页面内容
</div>
</Router>
4.使用Link组件作为导航菜单(路由入口)
<Link to ="/first">页面一</Link>
5.使用Route组件路由规则和要展示的组件(路由出口)
const First =()=><p>页面一的页面内容</p>
<Router>
<div className="APP">
<Link to="/first">页面一</Link>
<Route path="/first" component={First}></Route>
</div>
</Router>
常用组件说明
Route组件:包裹整个应用,一个React应用只需要使用一次
两种常用Router:HashRouter和BrowserRouter
HashRouter:使用URL的哈希值实现(localhost:3000/#/first)
(推荐)BrowserRouter:使用H5的historyAPI实现(localhost:3000/first)
Link组件:用于指定导航链接(a标签)
// to属性:浏览器地址栏中的pathname(location.pathname)
<Link to="/first">页面一</Link>
Route组件:指定路由展示组件相关信息
//path属性:路由规则
// component属性:展示的组件
// Route组件写在哪,渲染出来的组件就展示在哪
<Route path="/first" component={First}></Route>
路由的执行过程
1.点击Link组件(a标签),修改了浏览器地址栏中的url
2.React路由监听到地址栏url的变化
3.React路由内部遍历所有Route组件,使用路由规则(path与)pathnane进行匹配
4.当路由规则(path)能够匹配地址栏中的pathname时,就展示该Route组件的内容
编程式导航
场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如果实现?
编程式导航:通过JS代码来实现页面跳转
history是React路由提供的,用于获取浏览器历史记录的相关信息
push(path):跳转到某个页面,参数path表示要跳转的路径
go(n):前进或后退到某个页面,参数n表示前进或后退页面数量(比如:-1表示到上一页)
class Login extends Component{
handleLogin = ()=>{
// . . .
this.props.history.push('/home')
}
render(){…省略其他代码}
}
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import {BrowserRouter as Router,Route,Link} from 'react-router-dom'
class Login extends React.Component{
handleLogin=()=>{
//使用编程式导航来实现显示路由
this.props.history.push('/home')
}
render(){
return(
<div>
<p>登录页面:</p>
<button onClick={this.handleLogin}>登录</button>
</div>
)
}
}
const Home =props=> {
const handleBack=()=>{
//go(-1)表示返回上一个页面
props.history.go(-1)
}
return(
<div>
<h2>我是后台首页</h2>
<button onClick={handleBack}>返回登陆页面按钮</button>
</div>
)
}
const App=()=>(
<Router>
<div>
<h1>
编程式导航:
</h1>
<Link to="/login">去登陆页面</Link>
<Route path="/login" component={Login}/>
<Route path='/home' component={Home}/>
</div>
</Router>
)
ReactDOM.render(<App/>,document.getElementById('root'))
默认路由
问题:现在的路由都是点击导航菜单后展示的,如何在进入页面的时候就展示呢?
默认路由:表示进入页面时就会匹配路由
默认路由path为:/
<Route path="/" component={Home}/>
匹配模式
模糊匹配模式
问题:当Link组件的to属性值为"/login"时,为什么默认路由也被匹配成功?
默认情况下,React路由是模糊匹配模式
模式匹配规则:只要pathname以path开头就会匹配成功
<Link to="/login">登录页面</Link>
<Route path="/" component={Home}/>匹配成功
// path代表Route组件的path属性
// pathname代表Link组件的to属性(也就是location.pathname)
path |
能够匹配的pathname |
/ |
所有pathname |
/first |
/first或/first/a或/first/a/b |
精确匹配
问题:默认路由任何情况下都会展示,如何避免这种问题?
给Route组件添加exact属性,让其变为精确匹配模式
精确匹配:只有当path和pathname完全匹配时才会展示该路由
// 此时,该组件只能匹配pathname="/"这一种情况
<Route exact path="/" component=…>
推荐:给默认路由添加exact属性
React路由基础总结
1.React路由可以有效的管理多个视图(组件)实现SPA
2.Router组件包裹整个应用,只需要使用一次
3.Link组件是入口,Route组件是出口
4.通过props.history实现编程式导航
5.默认模糊匹配,添加exact变精确匹配
6.React路由的一切都是组件,可以像思考组件一样思考路由