class
class是es6的语法糖
什么叫做语法糖:便捷写法
最终还是会运行成es5构造函数语法
class Cat {
constructor (kind) {
this.kind = kind // 这里的this指向的是实例上 挂载在实例上
}
act () { // 这里的act挂载在原型上
console.log('我是猫,我会叫!喵~ ~ ~')
}
}
const p1 = new Cat('猫')
p1.act()
console.log(p1)
方法的不同写法和this的指向:
class Cat {
constructor (kind) {
this.kind = kind // 这里的this指向的是实例上 挂载唉实例上
}
act () { // 这里的act挂载在原型上
console.log('我是猫,我会叫!喵~ ~ ~,this的指向是')
console.log(this)
}
test = () => { // 挂载在实例上
console.log('这是一个箭头函数,this的指向是')
console.log(this)
}
}
const p1 = new Cat('猫')
p1.act()
console.log(p1)
继承
class Cat {
constructor (kind) {
this.kind = kind // 这里的this指向的是实例上 挂载在实例上
}
act () { // 这里的act挂载在原型上
console.log('我是猫,我会叫!喵~ ~ ~')
}
}
const p1 = new Cat('猫')
p1.act()
// console.log(p1)
// 下面介绍一下继承
class BlueCat extends Cat{
constructor (name, age, kind) {
// super 代表调用父类的constructor
super(kind)
this.name = name
this.age = age
}
actBlue () {
console.log('我是蓝猫,我的毛发是蓝色的,我也会喵喵喵哦~ ~ ~')
}
}
const p2 = new BlueCat('蓝色英短', 4 ,'猫')
p2.actBlue()
console.log(p2)
静态属性
安装
npm i create-react-app -g
创建项目
create-react-app 项目名称
项目运行主要依赖包
react核心语法包(虚拟dom jsx)
react-dom(多个组件虚拟dom结构 编译 挂载到#root上)
react-scripts 隐藏webpack的配置
jsx
视图结构通过虚拟dom实现
vue 和 react都各自提供了友好的虚拟dom编写方式
vue通过vue-loader编译 单文件组件中的template标签中的结构
react通过在js中直接写标签 react包自动分析在js中的标签结构
react中在js中写的标签是虚拟dom对象
xml in js
xml 可以理解为自定义标签
原来用于前后端传输数据
jsx中使用js表达式
使用花括号包裹,在花括号中使用表达式
import ReactDOM from 'react-dom'
const msg = '这是一个msg'
const tpl = (
<h1>Hello, world!!!{msg}
{/* 这是一个注释 */}
</h1>
)
ReactDOM.render(
tpl,
document.getElementById('root')
);
如何在vscode中 打开在react的js中打开对于emmet语法支持
记得重启!
组件
vsCode安装
React-Native/React/Redux snippets for es6/es7
组件要求首字母大写
函数式函数有先天缺陷 没有内部状态 没有生命周期
函数式组件
import ReactDOM from 'react-dom'
const App = () => {
return (
<div>
<h1>Hello, world!!!
{/* 这是一个注释 */}
</h1>
</div>
)
}
ReactDOM.render(
<App/>,
document.getElementById('root')
);
import ReactDOM from 'react-dom'
const App = (props) => {
console.log(props)
return (
<div>
<h1>Hello, world!!!
{/* 这是一个注释 */}
</h1>
</div>
)
}
ReactDOM.render(
<App title="这是一个title" num="这是一个number"/>,
document.getElementById('root')
);
class 组件
新建一个class App
import React, { Component } from 'react'
class App extends Component {
render(){
console.log(this)
return(
<div>
<h1>这是一个子组件</h1>
// 拿到传过来的title值 渲染进页面
<h2>{this.props.title}</h2>
</div>
)
}
}
export default App
放入ReactDOM.render中显示
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
<App title="这是一个title"/>, // 相当于 new App() 得到组件实例 并通过实例调用render方法(返回虚拟dom)
document.getElementById('root')
);
class App中console.log(this)打印结果如下:
this指向的是App的实例
标签上传递的参数在实例的props上
组件中 模板的样式处理
内联
通过对象的形式来定义style属性(原因:标签其实是虚拟dom对象)
使用时通过{ }
写样式的时候 宽高或者其他可以省略单位 例如:100px -> 100 100+100 -> (100+100)px
{ } 一个花括号的时候会被当作在js中写表达式
正确语法:
{{width:100,height:100+100,backgroundColor:"pink"}}
import React, { Component } from 'react'
class App extends Component {
render(){
console.log(this)
return(
<div>
<h1>这是一个子组件</h1>
<h2 style={{width:100,height:100+100,backgroundColor:"pink"}}>这是一个处理样式测试的 h2 </h2>
</div>
)
}
}
export default App
外部样式
css
新建一个css文件
定义className="test"
import React, { Component } from 'react'
class App extends Component {
render(){
console.log(this)
return(
<div>
<h1>这是一个子组件</h1>
<h2 style={{width:100,height:100+100,backgroundColor:"pink"}}>这是一个处理样式测试的 h2 </h2>
<div className="test"></div>
</div>
)
}
}
export default App
引入
import ReactDOM from 'react-dom'
import App from './App'
// 引入
import './assets/css/a.css'
ReactDOM.render(
<App title="这是一个title"/>, // 相当于 new App() 得到组件实例 并通过实例调用render方法(返回虚拟dom)
document.getElementById('root')
);
显示
sass
注意事项:react中自安装了一个node-sass,运行时会报错,解决方法:1 在package-lock.json中找到node-sass删除,然后删除node_modules文件夹,然后执行npm i重新下载所有依赖,然后下载npm i sass -D即可
新建一个scss文件
然后
<div className="test01"></div>
在index.js中引入
import './assets/sass/b.scss'
运行显示
styled-components基础使用
原理:
每一个区块的布局容器定义成样式组件
下载:
npm i styled-components -S
普通案例
新建一个js文件,定义盒子样式,并且导出
import styled from 'styled-components'
const Box = styled.div`
width:200px;
height:200px;
background:red;
`
export {
Box
}
导入
render(){
console.log(this)
return(
<div>
<Box></Box>
</div>
)
}
嵌套案例
定义导出Box01
const Box01 = styled.div`
width:200px;
height:200px;
background:red;
p{
color:#fff;
}
`
导入使用
<Box01>
<p>这是嵌套的p标签</p>
</Box01>
显示
传递参数
<Box01 bgc="#000">
<p>这是嵌套的p标签</p>
</Box01>
const Box01 = styled.div`
width:200px;
height:200px;
background:${props => props.bgc?props.bgc:'red'};
p{
color:#fff;
}
`
继承
const Box01 = styled.div`
width:200px;
height:200px;
background:${props => props.bgc?props.bgc:'red'};
p{
color:#fff;
}
`
const Box02 = styled(Box01)`
border:10px solid green
`
render(){
console.log(this)
return(
<div>
{/* <Box></Box> */}
<Box01 bgc="#000">
<p>这是嵌套的p标签</p>
</Box01>
<Box02></Box02>
</div>
)
}
动画
const move = keyframes`
0%{
transform:rotate(45deg)
}
100%{
transform:rotate(-45deg)
}
`
const Box01 = styled.div`
width:200px;
height:200px;
background:${props => props.bgc?props.bgc:'red'};
animation:${move} 1s infinite;
p{
color:#fff;
}
`
jsx原理
react在js执行之前,会分析jsx结构,并调用react.createElement将jsx编译成虚拟dom
React.createElement('div',
{
className:"box",
id:"test"
},
[React.createElement('p',{className:"testSon01"},["这是p"]),
React.createElement('p',{},["这是p"])
])
props验证
安装
prop-types包
npm install --save prop-types
import PropTypes from 'prop-types';
class MyComponent extends React.Component {
render() {
// ... do things with the props
}
}
具体验证规范
MyComponent.propTypes = {
// 基础类型验证
optionalArray: PropTypes.array,
optionalBigInt: PropTypes.bigint,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 必须是react组件 加标签 本质穿的是组件内部jsx
optionalElement: PropTypes.element,
// 必须时react组件 不加括号 使用时 <this.props.j/>
optionalElementType: PropTypes.elementType,
// 必须是对象 必须时Message的实例
optionalMessage: PropTypes.instanceOf(Message),
// 值必须是括号内多个中的一个
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 必须时数字的数组
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 对象里每一个属性都是数字
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 修饰 属性符合规范
optionalObjectWithShape: PropTypes.shape({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// 必须时对象 只能用给定属性 属性值必须复合要求
optionalObjectWithStrictShape: PropTypes.exact({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// 链式调用 验证数据类型 同时必穿 适用于以上所有验证
requiredFunc: PropTypes.func.isRequired,
};
默认props
当 不传d值时,依然可以使用this.props.d 默认值为 'xxxxxx'
class App extends Component {
render(){
console.log(this)
return(
<div>
111
</div>
)
}
}
App.defaultProps={
d:'xxxxxx'
}
props.children
相当于插槽
<App>
<div>
hello it is props.children!
</div>
</App>
class App extends Component {
render(){
console.log(this)
return(
<div>
{this.props.children}
</div>
)
}
}
state
props组件数据管理 用于管理外部数据的属性
state属性用于管理内部数据
render(){
this.state = {
msg:'这是一个示例变量'
}
console.log(this)
return(
<div>
{this.props.children}
{this.state.msg}
</div>
)
}
事件
class App extends Component {
render(){
this.state = {
msg:'这是一个示例变量'
}
console.log(this)
return(
<div>
{this.props.children}
{this.state.msg}
<button onClick={this.act}>点击事件</button>
<button onClick={this.act01}>点击事件</button>
</div>
)
}
act = () => { // 挂载实例上
console.log(this)
}
act01 = function() { // 挂载原型上
console.log(this)
}
}
输出
setState修改状态刷新视图
react不是mvvm框架,刷新试图下面几种情况:
组件中某一个props改变 立即刷新
调用setState方法 修改组件state
强制刷新 this.forceUpdate()
打印act = () => { console.log(this) }:
import React, { Component } from 'react'
class App extends Component {
constructor () {
super()
this.state = {
msg:'这是一个示例变量',
isBeatify:true
}
}
render(){
return(
<div>
{this.state.isBeatify?'这是一个美女':'这是一个好女孩'}
</div>
)
}
// 参数是函数
act = () => {
this.setState((state, props) => {
return {
isBeatify:!state.isBeatify
}
})
}
// 或者 参数是对象
act = () => {
this.setState({
isBeatify:!this.state.isBeatify
})
}
}
export default App
setState修改数据和刷新视图的异步操作
setState修改数据值变化以及视图刷新有些场景是同步的有些是异步的
异步性能更高 大部分场景下是异步 只能react捕获不到场景下调用setState才是同步
以下场景是异步:react可以捕获到的操作就是异步
1 react自己的合成事件(例如:onClick)事件函数中调用是异步
2 react生命周期钩子函数中调用是异步的
以下场景是同步的:react捕获不到的操作(不是react语法)
1 原生的dom事件 dom.onclick
2 其他函数中 比如:定时器
从18.x开始都是异步的
当异步情况下如何想要立即获取修改后最新的值 或者获取最新视图更新的最新的dom该怎么办
setState第二个参数是回调函数
出发的时机:数据更新完成 dom更新完成后回调触发(就像vue的this.$nextTick)
调用setState修改数据后,想拿到最新的值或者最新的dom就在setState的第二个参数回调函数中获取即可
act = () => {
this.setState((state, props) => {
return {
isBeatify:!state.isBeatify
}
},()=>{
console.log(this.state.isBeatify)
})
}
对比props和state
props管理组件外部数据
state管理组件自己的数据
什么时候使用props 什么使用state
逻辑组件(路由组件)
ui组件(傻瓜组件 在逻辑组件中 使用子组件)
将所有的数据都放在逻辑组件中管理 业务代码都在逻辑组件实现
逻辑组件一般使用state
ui组件使用props
衍生的一些重要概念
单向数据流
父组件传递给子组件 子组件无法修改
状态提升
应该多个后代组件的数据 统统提升到父组件中管理
逻辑组件和ui组件
组件渲染数据语法
条件渲染
class Todo extends Component {
constructor() {
super();
this.state = {
msg: '参数一',
newMsg:'参数二',
changeMsg:'',
bool:true
};
}
render() {
return (
<div>
<p>{this.state.msg}</p>
<p>{this.state.newMsg}</p>
<button onClick={this.act}>改变新的msg的值</button>
<button onClick={this.act01}>点击显示隐藏</button>
{// 单分支
this.state.bool
&&
<div>
需要显示隐藏的内容!
</div>
}
{
// 双分支
this.state.bool
?
<div>这是双分支需要显示隐藏的内容01!</div>
:
<div>这是双分支需要显示隐藏的内容02!</div>
}
</div>
);
}
act = () => {
this.setState((state, props) => {
return {
msg:state.newMsg,
newMsg:state.msg
}
})
}
act01 = () => {
this.setState((state, props) => {
return {
bool:!state.bool
}
})
}
}
循环渲染
<div>
<ul>
{
this.state.arr.map((el,index) => {
return (
<li key="index">
{el}
</li>
)
})
}
</ul>
</div>
渲染富文本
content:'<div style="color:pink;">这是一个富文本内容!</div>'
<div dangerouslySetInnerHTML={{__html:this.state.content}}></div>
Fragment组件 做jsx根组件
react提供空的容器组件 一般用来作为jsx根组件
优势:实际渲染真实dom时 不会渲染任何标签(解决jsx嵌套过程 由于根元素的问题 导致多个无用的根标签的问题)
<Fragment>
<div dangerouslySetInnerHTML={{__html:this.state.content}}></div>
</Fragment>
componentDidMount
类似于mounted
react 事件
react结合原生事件 合成react事件 好处:事件函数可以绑定到组件实例上 调用setState修改数据 视图刷新
合成事件语法
on原生事件首字母大写
<button onClick={this.act}>改变新的msg的值</button>
绑定一定是一个函数 不能家括号调用
react事件函数的挂载方式
jsx行内新增箭头函数 充当事件函数
class Todo extends Component {
state = {
msg:'11111111'
};
render() {
return (
<div>
<div>
{this.state.msg}
</div>
<button onClick={
() => {
console.log(this) // 指向的是实例
this.setState((state,props) => {
return {
msg: '2222222'
}
})
}
}>点击</button>
</div>
);
}
}
注意:不推荐使用 jsx本质上是页面结构 函数里面要写js业务逻辑 这样写会造成结构和js业务逻辑混乱,不利于代码的维护
其他三种绑定事件的方式
事件函数挂载到原型上
注意:原型上面的方法不一定指向的就是实例,而是要看谁调用的 大部分情况下使用this.xxx调用的方法 这种情况下原型的方法指向实例Person
Person.prototype.xxx() 调用指向原型空间
事件函数调用并不是实例调用而是合成事件触发让它调用 this指向react变成undefined
解决方案:行内通过bind改变this的指向
<button onClick={
this.clickBtn.bind(this)
}></button>
缺点:
bind在render中调用,render在初始化和数据更新都会触发 导致多次调用bind 每一次都返回一个函数的副本浪费内存哦
将事件挂载到原型,constructor的this改变指向
原理:constructor在实例上定义一个方法 值为原型上这个方法改变this的指向 意味着原型和实例上都有一个这个方法 实例上这个this指向实例 由于作用域关系 this调用这个方法时 一定拿到的是实例上面的方法
使用箭头函数 直接将方法定义到实例上
clickBtn = () => {
}
react事件对象
react合成事件的事件函数中 事件函数的第一个参数就是事件对象
e.stopPropagation() 阻止冒泡
e.preventDefault() 阻止默认事件
e.target 获取事件源
react事件传参
<button onClick={
(e) => {
this.clickBtn(参数,e)
}
}></button>
clickBtn = (参数,e) => {
}
输入框
value和变量进行双单向绑定
import React, { Component } from "react";
class Todo extends Component {
constructor () {
super()
this.state = {
msg:'1'
};
}
render() {
return (
<div>
<input value={this.state.msg} onChange={this.inputhandle}/>
{this.state.msg}
</div>
);
}
inputhandle = (e) => {
console.log(e.target.value)
this.setState(
{
msg:e.target.value
}
)
}
}
export default Todo;
class组件生命周期基础
生命周期基础
初始化阶段
constructor() static getDerivedStateFromProps() 相当于计算属性 render() 将jsx调用createElement渲染成虚拟dom componentDidMount() 初始化完成 可以在这里发送请求和获取dom
更新阶段
static getDerivedStateFromProps() shouldComponentUpdate() render() componentDidUpdate() 最新dom构建完成
注意:常用的是componentDidUpdate() 可以在这里获取最新dom,实际开发不建议在这里胡获取,会造成代码割裂,推荐在this.setState的回调函数中获取
卸载阶段
componentWillUnmount()
组件卸载时将组件实例从内存中移出,在组件初始化如果在全局上定义了一些特性的比如说定时器等,是不会共同移出的,组件卸载之前,手动在这个钩子中清除移出这些特性
生命周期高级部分
static getDerivedStateFromProps()
实现类似于vue中计算属性的功能 基于已有的props或者已有的state 计算得到一个新的state(初始化这些新的state是不在实例的state中的)
static getDerivedStateFromProps(props,state) props 定义初始state
return一个对象:
对象中的属性会自动添加到组件的state属性上
static getDerivedStateFromProps(props,state){
return {
changeProps:props.title,
changeMsg:state.msg*100
}
}
挂载到了实例上的state中
react更新机制
在react组件中 只要祖先组件更新了 不管有没有在后代中使用 后代组件默认都会更新
导致很大的性能浪费
解决:react将后代组件更新权力放给了开发者
开发者不处理:只要祖先组件更新了 不管有没有在后代中使用 后代组件默认都会更新
shouldComponentUpdate() 性能优化
方法:使用更新阶段生命周期钩子函数shouldComponentUpdate()
shouldComponentUpdate(nextProps,nextState) nextProps 更新之后的props nextState更新之后的nextState
shouldComponentUpdate(nextProps,nextState)返回值是一个布尔值:true 祖先组件更新 后代永远更新 false 永远不更新
更新的方案:祖先组件更新了 应该判断 内部的新旧props有没有发生变化
怎么办?
注意:不能直接比较props,因为引用类型比较的地址
1 先判断后代组件中有哪些props可能发生变化
2 直接比较这些props
3 值不一样 true 值一样 false
子组件继承 PureComponent 解决性能优化
原理:PureComponent会对props和state进行浅层比较 并且减少了跳过必要更新的可能性。
react 传递props 展开对象
obj={
a:1,b:2,c:3
}
<Todo {...obj}/>
等同于:
<Todo a={obj.a} b={obj.b} c={obj.c}/>
ref转发组件实例和dom对象
import React, { Component, createRef } from "react";
import TodoItem from './TodoItem'
class Todo extends Component {
constructor () {
super()
this.toRef = createRef()
this.btnRef = createRef()
}
componentDidMount () {
console.log(this.toRef)
console.log(this.btnRef)
this.btnRef.current.style.background='red'
console.log(this.toRef.current)
}
render() {
return (
<div>
<TodoItem ref={this.toRef}/>
<button ref={this.btnRef}>点击按钮</button>
</div>
);
}
}
export default Todo;
import React,{ Component } from "react";
class TodoItem extends Component {
constructor () {
super()
this.state={
msg:'这是TodoItem的msg'
}
}
render() {
return (
<div>这是TodoItem</div>
);
}
}
export default TodoItem;
react组件通信
props
ref
兄弟组件通信
context 进行组件通信
目录中新建一个文件夹context 文件夹中新建一个index.js
import { createContext } from "react";
// 创建一个context对象
const context = createContext()
// 这个对象下有两个属性
// Provider react组件 value属性提供数据 提供的数据只能由他的后代组件 通过Comsumer来获取
// Comsumer 任意组件 用来获取Provider提供的数据的组件
const {Provider , Consumer} = context
export{
context,
Provider,
Consumer
}
将父组件使用Provider包裹
const data={
a:10,
b:20,
changeA(n){
data.a = n
console.log(data.a)
}
}
<Provider value={data}>
<Todo title="index传递的props值"/>
</Provider>
子组件中 根标签为Consumer包裹
render() {
return (
<Consumer>
{
(data) => {
return (
<div>
<h1>A组件</h1>
{data.a}
<button onClick={
() => {
data.changeA(5)
this.forceUpdate()
}
}>点击修改A的值</button>
</div>
)
}
}
</Consumer>
);
}
context高级用法
import { createContext, Component, Fragment } from "react";
// 创建一个context对象
const context = createContext()
// 这个对象下有两个属性
// Provider react组件 value属性提供数据 提供的数据只能由他的后代组件 通过Comsumer来获取
// Comsumer 任意组件 用来获取Provider提供的数据的组件
const {Provider , Consumer} = context
// 定义封装class组件 封装provider
class ContextProvider extends Component{
state={
a:10,
b:20
}
render(){
return(
<Fragment>
<Provider value={{...this.state, changeA:this.changeA}}>
{this.props.children}
</Provider>
</Fragment>
)
}
changeA = (n) => {
this.setState({
a:n
})
}
}
export{
context,
Provider,
Consumer,
ContextProvider
}
<ContextProvider>
<Todo title="index传递的props值"/>
</ContextProvider>
render() {
return (
<Consumer>
{
(data) => {
return (
<div>
<h1>A组件</h1>
{data.a}
<button onClick={
() => {
data.changeA(5)
// this.forceUpdate()
}
}>点击修改A的值</button>
</div>
)
}
}
</Consumer>
);
}
高阶组件
高阶函数
function fn(a){
return funciton(b){
return a*b
}
}
函数柯里化 外层函数为高阶函数
高阶组件:
使用场景:修饰普通组件 给普通组件添加公共的视图结构 或 添加一些额外的props
实现步骤:本质上高阶组件是一个高阶函数 接受一个参数(就是要被修饰的普通组件) 函数返回一个组件
高阶组件命名:with+高阶组件功能名字
withTpl.js
import React, { Fragment } from 'react'
const withTpl = (Decorator) => {
return (props) => {
console.log(props,'222222222')
return (
<Fragment>
<h1>公共头部分</h1>
<Decorator {...props} c="dffgdfg">
</Decorator>
<h1>公共尾部分</h1>
</Fragment>
)
}
}
export default withTpl
子组件
import React, { Component } from "react";
// import { Consumer } from "../context/index";
import withTpl from '../hoc/withTpl'
class TodoA extends Component {
state = { }
render() {
console.log(this.props,'1111111111111')
return (
<div>
这是TodoA的页面
</div>
);
}
}
export default withTpl(TodoA);
父组件
<TodoA title="这是一个参数title"></TodoA>
<TodoB title="这是一个参数title"></TodoB>
函数式组件的学习
天生缺陷:没有内部状态 没有生命后期
在react16.8版本推荐react-hook钩子函数解决函数式组件缺陷
注意:hook函数一般use开头
所有hook只能在函数式组件中使用 且只能在函数式组件内部使用
useState
解决函数式组件没有内部状态问题
useState(10)定义初状态值为10
返回一个数组 [num,setState] 第一个是初始值 第二个是修改值得我方法
使用数组结构赋值 拿到初始值
修改值调用 第二个参数修改 视图自动刷新
注意:如果useState保存引用类型如 数组对象等 调用修改值得方法一定要传入新值(克隆),否则传递都是引用,比较认为没有变化 不会刷新视图
import React, { useState } from "react";
const TodoA = () => {
const [num,setState] = useState(10)
return(
<div>
这是TodoA
<br />
{num}
<button onClick={
() => {
setState(num+10)
}
}>点击修改值</button>
</div>
)
}
export default TodoA;
修改数组
import React, { useState } from "react";
const TodoA = () => {
let arr = [1,2,3,4,5,6,7,8,9,10]
const [arrTest,setState] = useState(arr)
return(
<div>
这是TodoA
<br />
<ul>
{
arrTest.map((el,index) => {
return (
<li key={index}>
{el}
</li>
)
})
}
</ul>
<button onClick={
() => {
setState([
...arrTest,arrTest.length+1
])
}
}>点击修改值</button>
</div>
)
}
export default TodoA;
useEffect解决生命周期钩子的问题
useEffect相当于componentDidMount和componentDidUpdate 初始化完成和更新完成都调用
import React, { useState,useEffect } from "react";
useEffect(()=>{
console.log('useEffect调用了') // 初始化的时候调用 每次修改数据的时候调用
})
只在初始化完成触发
useEffect还有第二个参数 是数组(定义修改时触发的依赖变量)
例如:[a,b] 此时只有a,b任意一个变量发生改变 回调才触发相当于侦听器
useEffect(()=>{ },[a,b])
卸载前的钩子
useEffect(()=>{
// 这个回调只会在初始化完成触发
return ()=>{
// 返回的函数 只会在卸载前触发
}
},[])
注意:useState的方法 修改数据和dom更新时异步拿到新的数据
应该在修改数据后 定义一个 类似侦听器useEffect在这里拿到最新的数据和dom即可
useContext
解决函数式组件获取context对象Provider提供数据的问题
useContext(context) // 返回该context对象Provider提供的value属性的值
import React, {useContext } from "react";
// import { Consumer } from "../context/index";
import { context } from '../context/index'
const TodoA = ()=>{
console.log(useContext(context))
const {a, changeA} = useContext(context)
return(
<div>
{a}
<br />
<button onClick={
()=>{
changeA(1665450)
}
}>点击修改a的值</button>
</div>
)
}
export default TodoA;
useRef
在函数式组件中 获取子组件实例(class组件) 和 dom对象
如果子组件式函数式组件怎么处理? forwardRef
高阶组件将子函数式组件绑定容器传递到子组件内部 子组件函数的第二个参数再次转发给子函数式组件内部的某个dom对象
import React, {useContext, useRef, useEffect } from "react";
import { context } from '../context/index'
import TodoC from "./TodoC";
const TodoA = ()=>{
const {a, changeA} = useContext(context)
const todoRef = useRef()
const btnRef = useRef()
useEffect(()=>{
console.log(todoRef.current)
console.log(btnRef.current)
},[])
return(
<div>
{a}
<br />
<button onClick={
()=>{
changeA(1665450)
}
}>点击修改a的值</button>
<hr />
<TodoC ref={todoRef}/>
<button ref={btnRef}>点击获取子组件的ref</button>
</div>
)
}
export default TodoA;
import React, {forwardRef} from "react";
const TodoC = forwardRef((props,btnRef) =>{
return(
<div>
这是TodoC
<button ref={btnRef}>点击获取</button>
</div>
)
})
export default TodoC
useMemo记忆函数
解决问题:在函数式组件中 祖先组件更新时 后代组件一定会重新调用使用(也会更新) 不同于class组件 这个子组件的调用时不可阻止的
性能问题:
useMemo 给定依赖 当useMemo发生改变时 useMemo回调出发 将子组件中业务函数在useMemo中触发 减少业务函数调用次数
回调初始化会触发一次 在更新时 依赖的props.b改变才会触发回调函数
useMemo(()=>{
fn()
console.log('触发了')
},[props.b])
return (
<div>
<h3>这是子组件</h3>
{props.b}
</div>
);
react-router
react-router
react-router-dom
react-router-native
思想:一切都是通过组件来实现路由定义 不同于 vue路由config形式
根组件
作为整个应用根组件而存在 否则路由不生效
hashRouter
BroswerRouter
路由上有basename属性 ‘/Admin’ 意思就是访问别的路径的时候要带上/Admin
Route定义路由组件
是路由定义也是当前路由组件出口
Route定义路由,有哪些属性:
path 定义路由路径:
注意:路由path匹配默认是地址栏路由 地址是以Route的path开头即可匹配
当匹配成功一个组件 不会停止匹配 继续向下匹配其他路由(贪婪模式)
解决:只需要引入Switch组件包裹路由即可
<Switch>
<Route path="/" exact component={Home}/>
<Route path="/news" component={News}/>
<Route path="/about" component={About}/>
</Switch>
component 定义路由组件
exact 精准匹配
Link
跳转路由
属性:
to:路由跳转的路由path
replace:跳转时覆盖当前历史记录
NavLink
跳转路由
具有Link所有的功能和属性
增加了导航高亮样式的处理
默认匹配导航 默认active类
activeClassName属性 修改高亮类
activeStyle 内联样式 定义高亮样式
exact 导航高亮样式 匹配 to 属性和地址一摸一样才能匹配
路由重定向和404
redirect 路由重定向
属性:
to:重定向地址
from:从哪儿来
exact:修饰from当前访问地址必须和from值一样时 开始重定向到to
404
路由提供了一个特殊的地址 * 匹配任意路由
<Route path="*" component={NotFound}>
注意:放置的位置在路由最后
嵌套路由
在父级路由组件中通过Route组件再定义子级路由
父级路由组件
<Route path="/news" component={News}/>
子级路由组件中
<Link to="/news/native">国内新闻</Link>
<Link to="/news/abroad">国外新闻</Link>
<Switch>
<Route path="/news/native" component={NativeNews}/>
<Route path="/news/abroad" component={AbroadNews}/>
<Redirect to="/news/native" from="/news" exact/>
</Switch>
注意:
子级路由path一定要携带一级路由地址作为前缀
父级路由Route组件一定不能精准匹配加exact属性
编程式导航
利用js api 进行导航跳转
注意:不管是声明式的Link还是NavLink一级编程式跳转api 参数都是一样
在react路由中
react给所有的组件路由props灌入了三大对象
注意:只有Route的component属性对应的组件才是路由组件
props中三个对象:
history 保存操作路由api
listen 监听路由
go(n) 0 刷新 1 前进一步 -1 回退1步
push 跳转路由
replace 跳转路由 覆盖当前历史记录
const { history } = this.props;
history.go(0) // 刷新
history.push("/home")
history.replace('/about')
非路由组件获取操作路由props
react-router提供了一个高阶组件
with Router使用这个高阶组件修饰普通组件即可获取
class组件constructor中如何获取props
问题:class组件在constructor触发时,props还未绑定到实例上
class App extends Component {
constructor(props){
super(props);
console.log(this.props,111);
};
}
针对函数式组件 编程式导航
hook函数 useHistory在函数式组件中快速拿到路由的history对象
const About = (props) => {
const history = useHistory();
console.log(history, 222);
return (
<div>
</div>
);
}
路由传参
注意:只说跳转参数和获取
动态路由传参
定义动态路由
<Route path="/news/:id" component={News}/>
history.push("/news/2")
参数获取 函数式组件和class组件都可以使用这个获取
this.props.match.params.参数名
函数式组件还可以使用useParams hook函数
import { useParams } from 'react-router-dom'
const params = useParams(); // {id: 2}
state传参
{
pathname: '/home',
state: {
a: 10,
b: 20
}
}
获取参数
this.props.location.state.xxx
函数式组件还可以使用 useLocation hook函数
search传参
在地址栏携带参数传递
跳转时path后携带search参数
/about?a=10&b=20
获取参数
this.props.location.search // 注意需要手动解析成对象才可以使用
qs插件
qs.stringify() qs将对象转成query格式
qs.parse() qs将query转成对象
Route组件的render属性
render值是一个函数 return jsx
当 当前路由匹配时 渲染render方法返回的jsx
<Route path="/news" render={(routeProps) => {
return <News {...routeProps}/>
}}/>
注意:由于使用render 目标组件不再是路由组件 是直接渲染的 render函数参数就是props三大对象 通过props传递的形式 传递给 目标路由组件
登录鉴权
使用redirect组件鉴权
原理:在某个需要登录的路由组件内部判断是否登录 没有登录则渲染 redirect组件进行重定向到登录页即可
const About = (props) => {
return (
<div>
{
!isLogin()
&&
<Redirect to="/login"/>
}
<h2>关于我们</h2>
</div>
);
}
利用render做登录鉴权
<Route path="/about" render={(routeProps) => {
if(isLogin()){
// 登录了
return <About {...routeProps}/>
}else{
// 没有登录
return <Redirect to={
{
pathname: '/login',
state: {
from: '/about'
}
}
}/>
}
}}/>
Redux
是JavaScript状态管理容器 提供可预测的状态管理
redux核心概念
单一状态树
state是只读的(只能由render来修改)
reducer必须是纯函数 什么是纯函数:函数返回结果一定依赖他的参数 且中间不能有副作用
基础使用
安装
npm i redux -S
创建仓库
引入创建仓库方法
import { legacy_createStore as createStore } from 'redux'
import { cloneDeep } from "lodash";
初始state
const defaultState = {
num: 10
}
定义reducer
const reducer = (state=defaultState, action) => {
// 对于state做深克隆
const newState = cloneDeep(state);
return newState
}
创建仓库
const store = createStore(reducer);
在组件中操作redux:
在组件获取state store.getState();
组件中提交action
/*
action是对象
固定属性
type 代表action 类型(action是干啥)
*/
store.dispatch({
type: 'ADD_NUM',
data: 5
})
组件中调用store.subscribe方法监听store中state的变化
// subscribe观察者,观察的就是 仓库中state的变化,变化后观察者回调触发
store.subscribe(() => {
this.setState({
...store.getState()
})
})
问题:
action每一次定义一个对象 没有任何复用性
action type属性是直接写的字符串(字符串写错了,reducer数据无法修改 且不报错)
单独抽离action使用函数封装 函数返回action 称这个函数为actionCreator
// actionCreators.js
import {ADD_NUM, REDUCE_NUM} from "./actionTypes"
const add_num = (data) => {
return {
type: ADD_NUM,
data
}
}
const reduce_num = (data) => {
return {
type: REDUCE_NUM,
data
}
}
解决type 单独在外部定义 常量 保存action的type reducer和action中都使用长量代表type
// actionTypes.js
export const ADD_NUM = 'ADD_NUM'
export const REDUCE_NUM = 'REDUCE_NUM'
reducer中判断type使用常量
import {ADD_NUM, REDUCE_NUM} from "./actionTypes";
const reducer = (state=defaultState, action) => {
const newState = cloneDeep(state);
switch (action.type) {
case ADD_NUM:
newState.num += action.data
break;
case REDUCE_NUM:
newState.num -= action.data
break;
default:
break;
}
return newState
}
组件中提交action
import {add_num, reduce_num} from '../store/actionCreators';
store.dispatch(add_num(5))
异步解决问题
store中某些state需要请求接口拿到数据 需要发送异步ajax
actionCreator中默认不能发送异步请求
解决:使用redux异步插件
redux-thunk
redux-promise
redux-saga 基于 es6 generator
redux-thunk插件
import { legacy_createStore as createStore, applyMiddleware } from 'redux'
import 插件1
import 插件2
const store = createStore(reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
applyMiddleware(插件1,插件2))
如何做异步处理:
增加了actionCreator的功能 原本必须函数return一个对象action
使用redux-thunk actionCreator可以return一个函数 内层内部可以发送异步请求 且内层函数里面有一个参数dispatch,就是store下的dispatch
具体实现:
import { legacy_createStore as createStore, applyMiddleware } from 'redux'
import { cloneDeep } from 'lodash'
import { ADD_NUM, INIT_DATA } from '../store/actionType'
import thunk from 'redux-thunk'
const defaultState={
num:10,
initNum:[]
}
const reducer=(state=defaultState, action)=>{
const newState = cloneDeep(state)
if(action.type === ADD_NUM){
newState.num += action.data
}else if(action.type === INIT_DATA){
newState.initNum = action.data
}
return newState
}
const store = createStore(reducer, // 只能有两个参数
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
applyMiddleware(thunk))
export default store
export const INIT_DATA = 'INIT_DATA'
import axios from 'axios'
import { INIT_DATA } from './actionType'
const fetch = (params)=>{
return dispatch => {
axios.get('https://api.it120.cc/conner/shop/goods/category/all',{params}).then(res=>{
console.log(res)
if(res.data.code === 0){
dispatch({
type:INIT_DATA,
data:res.data.data
})
}
})
}
}
export {
fetch
}
componentDidMount () {
store.dispatch(fetch({
page:1,
pageSize:10
}))
}
Redux-devtools
Chrome安装插件以后
在createStore第二个参数加上window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
const store = createStore(reducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
\