概括
React是一个用于构建用户界面的JavaScript库,MVC角度React是V,可以开发web应用、移动端原生应用(react-naive)、VR应用(react360)
特点
声明式
其结构与html一样
基于组件
组件是React最重要内容
组件表示页面中的部分内容
基本使用
安装
安装命令:npm i react react-dom
react包是核心,提供创建元素、组件等功能
react-dom包提供DOM相关功能
使用
1.引入react和react-dom两个js文件
2.创建react元素
3.渲染React元素到页面中
//引入文件
<script src="./node-modules/react/umd/reeact.development.js"></script>
<script src="./node-modules/react-dom/umd/reeact-dom.development.js"></script>
//react挂载位置
<div id="root"></div>
<script>
//创建react元素,(元素名称,元素属性,元素子节点...)(有更好的替代),元素属性可以是个对象,
count title = React.createElement('h1', null, 'Hello React')
//渲染react元素,(要渲染的react元素,挂载点)到页面挂载点上(重点方法)
ReactDom.render(title, document.getElementById('root'))
</script>
脚手架
初始化项目命令:npx create-react-app my-app
(my-app项目名称可修改)
启动项目命令(在根目录中):npm start
npx命令介绍
npx是npm v5.2.0引入的命令,主要为了提升包内提供的命令行工具的使用体验,以前需要先安装脚手架,使用npx无需安装脚手架包
脚手架中使用react
导入react包:import React from 'react'
导入react-dom包:import ReactDOM from 'react-dom'
调用React.createElement('h1', {hitle:'标题'}, 'Hello')
方法创建react元素(第一个参数:要创建的react元素名称,第二个参数:该react元素属性,第三个及以后参数:该react元素子节点)
调用ReactDOM.render()
方法把创建的react元素渲染到页面中(第一个元素:要渲染的react元素,第二个元素:dom对象,用于指定渲染到页面位置)
JSX语法基础
react脚手架中自动配置编译jsx语法包@babel/preset-react
jsx会被@babel/preset-react插件编译成createElement()方法
jsx可以使用html结构取代react创建元素,
创建react元素:const title = <h1>hello jsx</h1>
渲染react元素:ReactDOM.render(title, document.getElementById('root'))
注意点
react元素属性名使用驼峰命名法
特殊属性名:class>className,for>htmlFor,tabinndex>tabIndex
没有子节点双标签可以替换成单标签使用/>结尾
推荐使用()包裹JSX,避免js中自动插入分号陷进
嵌入js表达式
语法:{js表达式}
表达式中可以正常运行运算,方法和三元表达式,而且jsx自身也是一个表达式,但是js中对象例外,一般只出现在style属性中,而且if/for之类语句不能出现
条件渲染
可以使用if/else或三元运算符或逻辑与运算符(&&)来实现
列表渲染
渲染一组数据应该使用数组的map()
方法
渲染列表式应该添加key属性,key属性的值要保证唯一
map()遍历谁给谁添加key属性
尽量避免使用索引号作为key
const songs = [
{id: 1, name: "我和我的祖国"},
{id: 2, name: "中国机长"},
{id: 3, name: "攀登者"}]
{songs.map(item => <li key={item.id}>{item.name}</li>)}
样式处理
行内样式:react样式使用style行内式style={{color: ‘red’, color: ‘red’}},外部是条件表达式,内部是对象,对象属性名使用驼峰表达式
类名(推荐):className=“类名”,类名在css文件中定义,并需要import导入js文件中
react组件
两种创建组件方式:类组件和函数组件
函数组件:使用函数名作为组件标签名,函数组件必须有返回值,组件名称首字母必须大写用以区分普通react元素
// 写法一
const Hello = (props) => {
return <div>{props.message}</div>
}
// 写法二
const Hello = props => <div>{props.message}</div>
// 写法三
function Hello(props) {
return <div>{props.message}</div>
}
//渲染组件
ReactDOM.render(<Hello/>, document.getElementById('root'))
类组件:类组件就是基于 ES6 语法,类组件继承React.Component父类,从而可以使用父类中的方法或属性,类组件必须提供render()方法,render()方法必须有返回值,使用函数名作为组件标签名,类组件名称首字母必须大写
class Hello extends React.Component {
render() {
return (<div className="demoClass">类组件内容</div>)
}
}
//渲染组件
ReactDOM.render(<Hello/>, document.getElementById('root'))
创建单独组件:
创建js文件,import React from ‘react’(导入react)
,创建函数组件或类组件,export default 组件名(导出组件)
使用组件:
入口js文件中导入组件import 组件名 from ‘组件路径’(导入封装组件)
,然后渲染组件
事件处理
事件绑定
react事件绑定语法和dom事件语法相似
语法:on+事件名称={事件处理程序}(事件名称采用驼峰命名,类组件中事件处理程序前必须加this.)
事件对象
通过事件处理程序的参数获取事件对象
react中事件对象叫:合成事件(对象),合成事件兼容所有浏览器没有兼容问题
组件状态
状态(状态可变,数据驱动视图)
函数组件是无状态组件,负责静态结构展示,类组件是有状态组件,负责更新ui让页面动起来
state存储状态,setState修改状态
class Hello extends React.Component {
// 简化语法,初始化类组件的 state
state = {
text: '内容'
};
render() {
return (
<div className="demoClass" onClick={() => {this.setState({
text: "内容已经被改变"
})}}>类组件{this.state.text}
</div>)
}
}
state基本使用
状态即数据,状态时是私有的,只能在在组件内使用,通过this.state
获取状态
setAtate()修改状态
状态是可变的,通过`this.setAtate({要修改的数据}),不能直接修改state中的值
setState()作用是修改state,更新UI,以数据驱动视图变化
从jsx抽离事件处理程序
为了保证jsx结构清晰,将逻辑抽离到单独方法中,使用事件绑定到jsx中
注意:单独的方法中this指向undefind,jsx中使用箭头函数this指向外部环境即render方法,render方法this即组件实例
事件绑定this指向
箭头函数:箭头函数自身不绑定this,其this为外部环境this,render()方法this为组件实例
Function.prototype.bind():利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定
class Hello extends React.Component {
// 初始化类组件的 state的旧版语法
constructor() {
super()
this.state = {
text: '内容'
};
this.onIncrement = this.onIncrement.bind(this)
}
render() {
return (<div onClick={this.onIncrement}></div>)
}
}
class实例方法:利用箭头函数,属于实验性语法但有babel存在可以直接使用(推荐使用)
class Hello extends React.Component {
// 简化语法,初始化类组件的 state
state = {
text: '内容'
};
onIncrement = () => {this.setState({})}
render() {
return (<div onClick={this.onIncrement}></div>)
}
}
表单处理
受控组件(推荐使用)
react将state与表单元素值value绑定一起,由state值控制表单元素的值
使用步骤:
1.在state中添加一个状态,作为表单元素的value值(控制表单元素值得来源)
2.给表单元素绑定change事件,将表单元素值设置为state的值(控制表单元素值的变化)
state = {txt:''}
<input type="text" value={this.state.txt} onChange={e => this.setState({txt: e.target.value})} />
多表单优化:给表单元素添加name属性,名称与state相同
步骤:
1.给表单元素添加name属性,名称与state相同
2.根据表单元素类型获取对应值
3.在change事件处理程序中通过[name]来修改对应state
//步骤1
<input type="text" name="txt" value={this.state.txt} onChange={this.handleForm} />
//步骤3
hendleForm = e => {
//获取当前DOM对象
const target = e.target
//步骤2,根据表单元素类型获取值
const value = target.type == 'checkbox' ? target.checked : target.value
//获取name
const name = target.name
//根据name设置对应state
this.setState({
[name]: value
})
}
非受控组件
借助ref,使用原生DOM方式获取表单元素值,ref作用是获取DOM或组件
使用步骤:
1.调用React.createrRef()
方法创建一个ref对象
2.将创建好的ref对象添加到文本框
3.通过ref对象获取文本框的值
//步骤1
constructor() {
super()
this.txyRef = React.createRef()
}
//步骤2
<input type="text" ref={this.txtRef} />
//步骤3
console.log(this.txtRef.current.value)
组件传参
组件是封闭的,要通过props接收外部数据
传递数据:给组件标签添加属性
接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
//函数组件
<Hello name="lz" age={20}/>
function Hello(props) {
return <div>{props.name}</div>
}
//类组件
class Hello extends React.Component {
render() {
return <div>{this.props.age}</div>
}
}
props可以给组件传递任意类型数据(基本和复杂数据类型,html标签都可以),它是只读对象
使用类组件时,如果写了构造函数,应该将props传递给super(),否则无法在构造函数中获取到
class Hello extends React.Component {
// 初始化类组件的 state的旧版语法
constructor(props) {
//推荐将props传递给父类构造函数
super(props)
}
render() {
return <div>{this.props.age}</div>
}
}
组件通讯
父传子
1.父组件提供传递的state数据
2.给子组件标签添加属性,值为state中数据
3.子组件中通过props接收父组件中传递数据
//父组件
class Parent extends React.Component {
state = { lastName: 'lz'}
render() {
return (<div><Child name={this.state.lastName} /></div>)
}
}
//子组件
function Child(props) {
return <div>{props.name}</div>
}
子传父
利用回调函数,父组件提供回调函数,子组件调用,将要传递数据作为回调函数的参数
1.父组件提供一个回调函数(用于接收数据)
2.将该函数作为属性的值,传递给子组件
3.子组件通过props调用回调函数
4.将子组件数据作为参数传递给回调函数
//父组件
class Parent extends React.Component {
//提供回调函数,用来接收数据
getChildMsg = (msg) => {}
render() {
return (<div><Child getMsg={this.getChildMsg}/></div>)
}
}
//子组件
class Child extends React.Component {
state = {childMsg: 'react'}
handleClick = () => {
this.props.getMsg(this.state.childMsg)
}
render() {
return (<div onClick={this.handleClick}></div>)
}
}
兄弟组件
将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
公共父组件职责:提供共享状态,提供操纵共享状态的方法
要通讯子组件只需通过props接收状态或操作状态的方法
Context
嵌套多层组件首尾传递数据使用Context,可以跨组件传递数据
使用步骤:
1.调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件
2.使用Provider组件作为父节点
3.设置value属性,表示要传递数据
4.调用Consumer组件接收数据
//步骤1
const {Provider, Consumer} = React.createContext()
//步骤2
<Provider>最上层父组件中的子组件</Provider>
//步骤3
<Provider value="lz">
//步骤4
<Consumer>{data => <div>{data}</div>}</Consumer>
props深入
children属性
组件标签有子节点时,props就有该属性
children属性和普通props一样可以是任意值(文本、react元素、组件、函数)
校验
组件使用者传入数据,创建者不知道格式会传入错误会导致组件内部报错,且组件使用者不知确切错误原因
创建者创建组件时指定props类型、格式,报错时也能给出明确错误提示
使用步骤:
1.安装包prop-typesyarn add prop-types/npm i props-types
2.导入prop-types包
3.使用组件名.propTypes = {}
来给组件props添加校验规则
4.校验规则通过PropTypes对象来指定
import PropTypes from 'prop-types'
function App(props) {
return (<div>{props.colors}</div>)
}
App.propTypes = {
//约定colors属性为array类型,类型不对则报出明确错误,以便分析错误原因
colors: PropTypes.array
}
约束规则
1.常见类型:array、bool、function、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
})
props默认值
设置后组件不传入值会显示默认值,设置后以设置值为准
//设置默认值
App.defaultProps = {
pageSize: 10
}
组件生命周期
只有类组件有生命周期
创建时
三个钩子函数,执行顺序为:constructor() > render() > componentDidMount()
钩子函数 | 触发时机 | 作用
- | - | -
constructor | 创建组件是最先执行 | 初始化state,为事件处理程序绑定this
render | 每次组件渲染都会触发 | 渲染UI(不能调用setState(),否则会报错)
componentDidMount | 创建挂载(完成DOM渲染)后 | 发送网络请求,DOM操作
更新时
执行时机:setState()
、foreUpdate()
和组件接收到新props
执行顺序:render() > componentDidUpdate()
钩子函数 | 触发时机 | 作用
- | - | - |
render | 每次组件渲染都会触发 | 渲染UI
componentDidUpdate | 组件更新(完成DOM渲染)后 | 发送网络请求、DOM操作(如果是setState()则必须放在一个if条件中) |
卸载时
组件从页面消失时执行
钩子函数 | 触发时机 | 作用
- | - | -
componentWillUnmount | 创建卸载(从页面消失) | 执行清理工作(如清除定时器)
render-props和高阶组件
react组件复用
功能相似的多个组件复用,复用的是state和操作state的方法
两种方式:render props模式和高阶组件(hoc)两种方式不是新的api,只是演化的固定写法
render props模式
使用组件时添加一个值为函数的prop,通过函数参数获取(需在组件内实现)
使用该函数返回值作为要渲染的UI内容(需在组件内实现)
<Mouse render={(mouse) => {要渲染的UI内容}}/>
使用步骤
1.创建Mouse组件,在组件中中提供复用的状态逻辑代码(状态和操作状态的方法)
2.将要复用的状态作为props.render(srate)
方法的参数暴露到组件外
3.使用props.render()
的返回值作为要渲染的内容
class Mouse extends React.Component {
//省略state和操作state的方法
render() {
return this.props.render(this.state)
}
}
<Mouse render={(mouse) => <p>渲染内容</p>} />
children代替render
<Mouse>{(mouse) => <p>渲染内容</p>}</Mouse>
//组件内部
this.props.children(this.state)
代码优化
1.给render props模式添加props校验
2.组件卸载时解除mousemove事件绑定
//添加校验
Mouse.propTYpes = {
chidlren: PropTypes.func.isRequired
}
//卸载时事件
componentWillUnmount() {
window.removeEnentListener('mousemove', this,handleMouseMove)
}
高阶组件
高阶组件是一个函数,接收要包装组件,返回增强后组件
高阶组件内部创建一个类组件,在类组件中提供复用的状态逻辑代码,同过prop将复用的状态传递给被包装组件WrappedComponent
const EnhancedComponent = withHOC(WrappedComponent)
//高阶组件内部创建的类组件
class Mouse extends React.Component {
//省略state和操作state的方法
render() {
return <WrappedComponent {...this.state} />
}
}
使用步骤
1.创建一个函数,名称约定with开头
2.指定函数参数,参数大写字母开头(作为要渲染的组件)
3.函数内部创建一个类组件,提供复用的状态逻辑代码并返回
4.该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
5.调用该高阶组件,传入要增强组件,通过返回值拿到增强后组件,并将其渲染到页面中
function withMouse(WrappedComponent) {
class Mouse extends React.Component {
//省略state和操作state的方法
render() {
//Mouse组件的render方法中
return <WrappedComponent {...this.state} />
}
}
return Mouse
}
//创建组件
const MousePosition = withMouse(Position)
//渲染组件
<MousePosition />
设置displayName
使用多个高阶组件因为类组件名字相同所以组件名称相同,为了区分组件为高阶组件设置displayName便于调试时区分不同组件
//在高阶组件函数内,类组件后,return前,WrappedComponent为高阶组件函数形参
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent) {
renter WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
传递props
高阶组件渲染组件时添加props,但是渲染到高阶组件类组件中没有把值传递到类组件中被包装组件,想要解决需要在类组件中渲染WrappedComponent时将state和this.props一起传递给组件
<WrappedComponent {...this.state} {...this.props} />
setState说明
更新数据
setState()是异步更新数据,使用时不要前后依赖,多次调用只会触发一次渲染
推荐语法
使用setState((state, props) => {})
语法,state表示最新state,props表示最新props
this.setState((state, props) => {
return {
count: state.count + 1
}
})
第二个参数
在状态更新(页面重新渲染)后立即执行某个操作
this.setState(
(state, props) => {},
() => {此回调函数在状态更新后执行},
})
组件更新机制
setState()
的两个作用:修改state,更新组件(UI)
父组件重新渲染时会重新渲染子组件,但只渲染当前组件及所有子组件
组件性能优化
减轻state
state只存储与组件渲染相关数据(count/列表数据/loading等)
不用做渲染的但是需要在多个方法中用到的数据应该放到this中
class Mouse extends React.Component {
componentDidMount() {
//timerId存储到this中
this.timerId = setInterval(() => {}, 3000)
}
componentWillUnmount() {
clearInterval(this.trmerId)
}
render() {}
}
避免不必要渲染
组件更新机制中父组件更新会带动子组件更新
使用钩子函数shouldComponentUpdate(nextProps, nextState)
通过返回值是true或者false决定组件是否渲染,nextProps和nextState表示最新内容,更新阶段钩子函数在组件重新渲染前执行(shouldComponentUpdate > render)
class Mouse extends React.Component {
shouldComponentUpdate() {
//使用条件判断,决定是否重新渲染
return false
}
render() {}
}
纯组件
React.PureComponent与React.Component功能相似,React.PureComponent内部自动实现了shouldComponentUpdate钩子,纯组件内部分别对比前后两次props和state值来决定是否重新渲染组件
class Mouse extends React.PureComponent {
render() {
return (<div>纯组件</div>)
}
}
纯组件内部对比是shallow compare(浅层对比),普通数据类型可以直接使用,state或props是引用数据类型时应该创建新数据,不能修改原始对象中属性值
虚拟DOM和Diff算法
虚拟DOM:本质上是一个描述UI的js对象
执行过程:
初次渲染时,react根据初始state创建一个虚拟DOM对象(树)
根据虚拟DOM生成真正DOM,渲染到页面中
数据变化后重新根据新数据创建新的虚拟DOM对象(树)
前后虚拟DOM对象根据Diff算法对比,得到需要更新内容
react只将变化内容更新到DOM中,重新渲染到页面
组件render()调用后,根据状态和JSX结构生成虚拟DOM对象
虚拟DOM的最大意义是脱离了浏览器环境束缚,能运行js的地方就能运行react可以跨平台
路由
概述
前端路由是一套映射规则,在React中式URL路径与组件的对应关系
使用
1.安装:yarn add react-router-dom
2.导入路由三个核心组件:import { BrowserRouter as Router, Router, Link } from 'react-router-dom'
3.使用Router组件包裹整个应用:<Router>根组件</Router>
(一个react应用只需要使用一次)
4.使用Link组件作为导航菜单(路由入口):<Link to="/路径">菜单内容</Link>
5.使用Route组件配置路由规则和要展示组件(路由出口):<Route exact path="/路径" component={组件名}></Route>
(该组件指定路由展示组件相关信息,path属性:路由规则,component属性:展示的组件,exact属性用于精准匹配路由,react默认模糊匹配)
常用组件说明
Router组件:包裹整个应用,一个应用使用一次
两种Router:BrowserRouter使用H5的histtoryAPI实现不带#更加推荐使用,HashRouter使用URL的哈希值实现带#
Link组件:用于指定导航链接(a标签)(to属性:浏览器地址栏中的pathname(location.pathname))
Route组件:指定路由展示组件相关信息(path属性:路由规则,component属性:展示的组件,exact属性用于精准匹配路由,react默认模糊匹配)
路由执行过程
1.点击Link标签(a标签),修改浏览器地址栏中的url
2.React路由监听到地址栏url变化
3.React路由内部遍历所有Route组件,使用路由规则(path)与pathname进行匹配
4.路由规则(path)能够匹配地址栏pathname时,就展示该Route组件内容
编程式导航
编程式导航:通过js代码实现页面跳转
history是react路由提供,用于获取浏览器历史记录的相关信息
push(path):跳转到某个页面,参数path表示要跳转的路径
go(n):前进或后退到某个页面,参数n表示前进或后退页面数量
this.props.history.push('/路径')
this.props.history.go(1/-1)
默认路由
默认路由:进入页面时就会展示的页面
<Route path="/" component={组件名}></Route>
路由重定向:Redirect组件用于实现路由重定向,to指定跳转到的路由地址(Redirect添加到Route导入中)
<Route exact path="/" render={() => <Redirect to="/home" />} />
匹配模式
模糊匹配模式
React路由默认是模糊匹配模式
模糊匹配规则:pathname以path开头就会匹配成功
path(代表Route组件的path属性) | pathname(代表Link组件的to属性) |
---|---|
/ | 所有pathname |
/first | /first或/first/a |
精确匹配模式(推荐)
精确匹配模式:给Route组件添加exact属性
精确匹配:path和pathname完全匹配时才会展示该路由
<Route exact path="/路径" component={组件名}></Route>