有状态是复杂组件
无状态是简单组件
state简写方式
class Demo extends React.Component{
// 初始化状态
state = {isState:false}
render(){
const {isState} = this.state
return(<div>state简写{isState}</div>)
}
}
ref的写法方式
<input ref={(current)=>{this.refDom = current}}/> // 不推荐 但也无关紧要
<input ref={this.saveRefInput}/> // 推荐
saveRefInput = (c) => {
this.input = c
console.log(c)
}
// 最新 createRef 形式
myRef = React.createRef // 该容器是一对一的形式
<input ref={this.myRef}/>
console.log(this.myRef.current)
定时器需要在页面销毁时关闭
getDerivedStateFormProps 介于构造器和render之间
static getDerivedStateFormProps(props,state){return null}
// state的值在任何时候都取决于props
getSnapshotBeforeUpdate 介于render和update之间
getSnapshotBeforeUpdate(){return '任何返回值将作为参数传递给componedtDidUpdate'}
// 提交到DOM节点之前调用,更新之前获取一些信息(例如滚动位置)
遍历列表是最好不要用index作为key 会影响性能 使用每条数据对唯一标识作为key
使用index作为key可能会引发对问题:
- 若对数据进行 逆序添加,删除等破坏顺序操作 会产生没有必要的真实DOM更新==》界面效果没问题,但效率低
- 如果结构中还包含输入类的DOM 会产生错误DOM更新==》界面有问题
- 如果不存在对数据的逆序添加,删除等破坏操作,仅用于渲染展示,使用index作为key是没有问题的
react脚手架
- 全局安装 npm install -g create-react-app
- 切换到想创建项目的目录 再使用命令 create-react-app demo-react
- 进入项目 cd demo-react
- 启动项目 npm start
public文件夹里的文件:
- noscript 浏览器不支持js则展示标签中的内容
- robots.txt 爬虫规则文件
scr里的文件:
<React.StrictMode>
<App/>
</React.StrictMode>
// 可以检查代码里的小瑕疵 让代码写的更加规范
reportWebVitasl.js页面性能检测
setupTest.js 用于组件测试
组件的文件后缀可以使用 .jsx
项目文件里的引用顺序:
- 第三方包放最上面
- 自己写的的包放下面
- style文件放最下面
往数组里添加数据
const {todos} = this.state;
const todoObj = {}
const newTodos = [todoObj,...todos]
this.setState({
todos:newTodos
})
遍历数组更改相符合的一项返回,例如接收需要更改的id和要更改的状态
update = (id,done){
const {todos} = this.state;
const newTodos = todos.map(todoObj=>{
if(todoObj.id === id) return {...todoObj,done}
else return todoObj
})
// 更新数据状态
this.setState({
todos:newTodos
})
}
删除一个对象 用filter处理
delete = (id) => {
const {todos} = this.state;
const newTodos = todos.filter(todoObj=>{
return todoObj.id !== id
})
// 更新数据状态
this.setState({
todos:newTodos
})
}
数组 reduce 统计求和
// todos里的done如果完成了就+1 否则就+0
const doneCount = todos.reduce((pre,cur)=>{return pre + (todo.done ? 1 : 0)},0)
state在哪里 操作方法就在哪里
- react本身只关注界面,不包含发送ajax请求的代码
- 前端应用需要通过ajax请求与后台进行交互
- react应用轴需要集成第三方ajax库或自己封装
react脚手架配置代理
单个代理 package.json 添加 ‘proxy’:'127.0.0.1:8000’
多个代理 新增文件 setupProxy.js 写如下代码
const proxy = require('http-proxy-middleware')
module.exports = function(app){
app.use(
proxy('/api1',{ // 遇见/api1前缀的请求,就会触发该代理配置
target:'http://localhost:5000', // 请求转发给谁
changeOrigin:true, // 控制服务器收到的请求头中host字段的值
pathRewrite:{'^/api1':''} // 重写请求路径(必须)
}),
proxy('/api2',{
target:'http://localhost:5001',
changeOrigin:true,
pathRewrite:{'^/api2':''}
})
)
}
连续结构赋值
const {a:{b:{c}}} = obj
console.log(c)
连续解构赋值加重命名
const {a:b:data} = obj
console.log(data
)
三元表达式连写
isFirst ? <h1>1</h1> : isLoad ? <h2>2</h2> : err ? <h3>3</h3> : <h4>done</h4>
组件传值
父传子 子传父 props
兄弟组件传递数据
订阅消息:
1. 消息名
2. 发布消息
PubSubJS
npm install pubsub-js
兄弟1 发布消息
import PubSub from 'pubsub-js'
onSedData = () => {
const obj = {name:'xxx',age:18}
// 在任何非父子组件传递数据时都可使用
PubSub.publish('test',{obj})
}
兄弟2 订阅消息
import PubSub from 'pubsub-js'
componentDidMount(){
// 订阅消息
this.token = PubSub.subscribe('test',(_,data)=>{
console.log(data) // 收到数据
})
}
// 取消订阅
componentWillUnmount(){
PubSub.unsubscribe(this.token)
}
fetch请求 浏览器自带请求 不需要下第三方库
react路由
前端路由就是history
react-router-dom
npm install react-router-com
react靠路由链接切换组件
路由 # 后面属于 hash值(不会发给服务器) HashRoute
BrowsesRouter 不带 # 号 一般使用这种
路由组件和一般组件
- 写法不同
一般组件<Demo/>
路由组件<Route path="/demo" component={Demo}/>
- 存放位置
一般组件 components 放在项目文件里
路由组件 pages 放在项目文件里 - 接收到得props不同
一般组件 组件里传递了什么就能接收到什么
路由组件 接收到三个固定得属性 history location match
NavLink能自动为当前点击得标签添加 active class类名并且通过activeClassName修改
封装 NavLink
import React,{Component} from 'react'
import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component{
render(){
return(<NavLink activeClassName="newClass" className="listItem" {...this.props}/>)
}
}
使用
<MyNavLink to="/home" a={1} b={2}>Home</MyNavLink>
Switch的使用 阻止路由匹配到了相对应的路由不再往下匹配
引入 Switch 组件 使用 Switch 组件把路由包裹
- 通常情况下 path 和 component 是一一对应关系
- Switch 可以提高路由匹配效率(单一匹配)
解决多级路径刷新页面样式丢失的问题
public/index/html
中 引入样式时不写./
写/
(常用)public/index/html
中 引入样式不写./
写%PUBLIC_URL%
(常用)- 使用 `HashR路由
严格匹配与模糊匹配
- 默认使用的是模糊匹配 必须包含要匹配的路径 且顺序要一致
- 开启严格匹配 添加属性 exact
- 严格匹配不要随便开启,需要时再开,有些时候会导致无法继续匹配二级路由
嵌套路由
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
路由组件传递参数
- params参数
路由链接(携带参数):<Link to="/demo/tom/18">params参数</Link>
注册路由(声明接收):<Route path="/demo/:name/:age" component={Demo}/>
接收参数: const {name,age} = this.props.match.params
- search参数
引入库 import qs from 'querystring'
路由链接(携带参数):<Link to="/demo/?name=tom&age=18">search参数</Link>
注册路由(无需声明正常注册即可):<Route path="/demo" component={Demo}/>
接收参数: this.props.location.search
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
- state参数
路由链接(携带参数):<Link to={{path:'/demo',state:{name:'tom',age:18}}}>state参数</Link>
注册路由(无需声明正常注册即可):<Route path="/demo" component={Demo}/>
接收参数: const{name,age} = this.props.location.state || {}
备注: 刷新页可以保留住参数,参数不在地址栏上体现
编程式导航 借助this.props.history对象上的api进行操作
withRouter:
- 可以加工一般组件 可以让一般组件具备路由组件所特有的api
- withRouter返回值是一个新组件
BrowerRouter 与 HashRouter 的区别
- 底层原理不一样
BrowerRouter 使用的是 H5 的history api,不兼容ie9及以下的版本
HashRouter 使用的是url的哈希值 - path表现形式不一样
BrowerRouter 的路径中没有# 例如:localhost:3000/demo
HashRouter 的路径包含# 例如:localhost:3000/#/demo
- 刷新后对路由state参数的影响
BrowerRouter 没有任何影响,因为state保存在history对象中
HashRouter 会导致路由state参数的丢失 - 备注:HashRouter可以用于解决一些路径错误相关的问题
setState更新状态的两种写法
// 对象式的setState
this.setState({count:count+1},()=>{console.log(this.state.count)})
// 函数式的setState
this.setState((state,props)=>{return {count:state.count+1}})
lazyLoad
import React,{Component,lazy,Suspense} from 'react;
const Home = lazy(()=>import('./Home'))
<Suspense fallback={<h1>loading......</h1>}>
<Route path="/home" component={Home}/>
</Suspense>
Hooks
- 是react16.8版本新增加的新特性/新语法
- 可以在函数中使用state以及其他的react特性
三个常用的Hook
State Hook: React.useState()
- 让函数组件也可以有state状态,并进行状态数据的读写操作
- 语法:
const [xxx,setXxx] = React.useState(initValue) - userState()说明
参数:第一次初始化指定的值在内部操作
返回值:包含2个元素的数组,第一个为内部当前状态值,第二个为更新状态值的函数 - setXxx() 两种写法
setXxx(newValue): 参数为非函数值,直接指定新的状态值,内部用覆盖原来状态值
setXxx(value=>newValue): 参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
Effect Hook: React.useEffect()
- 让函数组件执行副作用操作(用于模拟类组件中的生命周期钩子)
- react中的副作用操作:
发ajax请求数据获取
设置订阅/启动定时器
手动更改真实DOM - 语法和说明:
useEffect(()=>{
// 在此可以执行任何带副作用操作
return()=>{
// 组件卸载前执行
// 在此做一些收尾工作,比如清除定时器/取消订阅等
}
},[stateValue]) // 如果指定的是[],回调函数只会在第一次render()后执行 - 可以把 useEffect Hook看作如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
Ref Hook: React.useRef()
- Ref Hook 可以在函数组件中存储。查找组件内部标签或任意其它数据
- 语法: const refContainer = useRef()
- 作用:保存标签对象,功能与React.createRef()一样
Fragment 文档碎片
等同于 <></>
区别 Fragment可以指定key
但是空标签不可以
不可以传值
Context 一种组件间通信方式 常用于 祖组件 与 后代组件 间通信
使用
// 创建Context容器对象
const XxxContext = React.createContext()
// 渲染子组件时,外面包裹XxxContext.Provider 通过value属性给后代组件传递数据
<XxxContext.Provider value={数据}></XxxContext.Provider>
// 后代组件读取数据
// 第一种方式 仅适用于类组件
static contextType = XxxContext // 声明接收context
this.context // 读取context中的value数据
// 第二种方式 函数组件与类组件都可以 value就是context中的value数据
<XxxContext.Consumer>
{value=>(要显示的内容)
</XxxContext.Consumer>
注意
:在应用开发中一般不用context,一般都用它的封装react插件
Component的问题
- 只要执行setState即使不改变状态数据,组件也会重新render
- 只当前组件重新render,就会自动重新render子组件==>效率低
原因:
Component中的shouldComponentUpdate总是返回true
解决:
- 重写shouldComponentUpdate方法,比较旧的state或props,如果有变化才返回true,否则false
- 使用 PureComponent(重写了shouldComponentUpdate)
注意:
只是进行state和props数据的浅比较,如果只是数据对象内部数据变了,返回false
不要直接修改state数据,而是要产生新数据
项目中一般使用PureComponent来优化
render props
如何向组件内部动态传入带内容的结构(标签)
vue中:
使用slot技术
react
使用children props: 通过组件标签传入标签
使用render props: 通过组件标签属性传入结构,一般用render函数属性
// children props
<A>
<B>xxxxx</B>
</A>
{this.props.children}
// 问题:如果B组件需要A组件内的数据,==>做不到
// render props
<A render={(data)=><C data={data}></C>}></A>
// A组件:{this.props.render(内部state数据)}
// C组件:读取A组件传入的数据显示:{this.props.data}
错误边界 Error boundary
特点
:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件合成事件,定时器中产生的错误
用于生产环境,开发环境如果有错误还是会出现错误
state = {
hasError:'', // 用于标示子组件是否产生错误
}
// 当parent的子组件出现报错的时候,就会触发,并携带错误信息
static getDerivedStateFromError(error){
return {hasError:error}
}
componentDidCatch(){
console.log('此处统计错误,反馈给后台')
}
render(){
return(<div>{this.state.hasError ? <p>当前网络不稳定请稍后再试</p> : <Child/>}</div>)
}
组件通信方式
组件间的关系:
父子组件
兄弟组件(非嵌套组件)
祖孙组件(跨级组件)
几种通信方式:
1.props
children props
render props
2. 订阅发布
pubs-sub event等等
3. 集中式管理
redux dva等等
4. conText
生产者–消费者模式
比较好的搭配方式
父子组件: props
兄弟组件: 消息订阅-发布,集中式管理
祖孙组件: 消息订阅-发布,集中式管理,conText(开发用得少,封装插件用得多)
通过 serve 可以运行前端打包文件