学习来源 B站尚硅谷的课程
react
概述
用于构建用户界面的js库
- react库的组成
核心库:
react.js -- react 核心库
react-dom.js -- 用于react操作DOM
babel.min.js -- 用于将jsx转化为浏览器可识别的js语言额外库:
prop-types -- 用于验证props属性,显示在log内给与开发者使用,通过静态引入,或者npm包安装查看react版本号
npm info react
- 查看react脚手架版本号
create-react-app --version
create-react-app -V
0-1. JSX语法
jsx语法是 一种react描述 组件html结构的开发语法
主要要求如下:
- 定义DOM元素时候,不能加引号
- 标签内混入JS表达式要用 { }
- 样式类名不能使用class关键字,而是使用className
- 不管是函数组件还是类组件,返回jsx结构只能有一个根标签
- 标签不管是双标签闭合,还是自闭合,必须闭合
- 组件标签 必须使用大写字母开头
- 小写字母开头标签,会自动查找html对应标签,如果没有就会报错
- 区别与传统js,事件使用驼峰命名,onclick => onClick
- prop-types 通过静态引入
<script src="prop-types.js"></script>
<script>
组件名.propTypes = {
属性名: PropTypes.string //属性需要为字符串类型
属性名: PropTypes.array //属性需要为数组类型
属性名: PropTypes.fuc //属性需要为函数类型
}
组件名.defaultProps = {
属性名: '缺省值' //属性缺省值配置
}
</script>
- propTypes 通过包安装
import PropTypes from 'prop-types';
//接收props组件内
export default class MyCmp extends Component {
static propTypes = {
xxx: PropTypes.func.isRequired // 传递至MyCmp组件上的属性xxx 类型为函数,且必填
}
static defaultProps = {
xxx: 123 //属性缺省值配置
}
}
- 函数式组件
定义函数返回一个结构,然后将这个结果的jsx,返回的虚拟DOM转化为真实DOM,渲染至容器DOM节点上
function Cmp1(){
return <h1>我是一个组件</h1>
}
ReactDOM.render(<Cmp1 />,document.getElementById('root'));
3-1. 函数式组件使用props
function Person(props){
console.log(props)
return <h6>hello world 类型{typeof props.myname} - {props.myname}</h6>
}
ReactDOM.render(
<Person myname={100} />,
document.getElementById('root')
);
HOOKS
- react 16.8 推出的语法新特性
- 常用的hook有:
react.useState -- 赋予函数式组件操作state的能力
react.useEffect -- 赋予函数式组件使用生命周期 ( componentDidMount, componentDidUpdate, componentWillUnmount )
react.useRef -- 赋予函数式组件操作元素节点的能力
3-2. 通过react.useState的hook,使得函数式组件拥有操作state的能力
import React from 'react'
import ReactDOM from 'react-dom'
function Funcmp(props){
//格式为数组,结构后,第一项为state,第二项为方法
// 修改一般数据
let [ num,changeNum ] = React.useState({ num:0 })
let [sName,changeName] = React.useState('zhangsan');
//方式一,对象式修改数据方法
function addFn(){
changeNum(num+1);
};
//修改对象数据
let [sNum,changeNum] = React.useState({num1:1,num2:1});
function changeNumFn1(){
//调用操作state的方法
changeNum({...sNum,num1:sNum.num1+1});
//方式二,函数式来修改数据方法
//函数的参数就是 state数据
changeNum((data)=>{
// console.log(data); //{num1: 1, num2: 1}
return {...data,num2:data.num2*2}
});
};
function changeNumFn2(){
//调用操作state的方法
changeNum({...sNum,num2:sNum.num2*2});
};
return (
<div>
<h6>当前的数值 { num } , { sNum.num1 }--{ sNum.num2 }</h6>
<button onClick={ addFn }></button>
<button onClick={ changeNumFn1 }>点击我改变数值1</button>
<button onClick={ changeNumFn2 }>点击我改变数值2</button>
</div>
)
};
3-3. 通过react.useEffect的hook,使得函数式组件拥有操作生命周期的能力
语法结构参数不同,可能出现的生命周期
react.useEffect(()=>{
console.log(1111);
return (()=>{
//此处为componentWillUnmount
})
},[ ])
- 如果react.useEffect的第二个参数为空数组,则函数内1111只会触发一次,此处为componentDidMount
- 如果react.useEffect的第二个参数没有,则函数内1111会触发1+N次,此处为componentDidMount+componentDidUpdate
- 如果react.useEffect的第二个参数为数组内有一项或以上,则针对这些项,为componentDidMount+componentDidUpdate
针对这些项目以外的项,为componentDidMount - react.useEffect第一个参数的返回的函数,为componentWillUnmount
3-4. 通过React.useRef 赋予函数式组件,通过ref获取DOM节点的能力
import React from 'react'
export default function Funcmp3() {
//设置state数据
let [inputVal,changeMes] = React.useState('');
let inputRef = React.useRef();
//通过ref获取DOM元素的value值
function showMes(){
console.log(inputRef.current.value);
}
//表单改变state
function changeMesFn(event){
changeMes(event.target.value)
}
return (
<div>
<input ref={inputRef} type="text" value={inputVal} onChange={changeMesFn} placeholder="输入些什么" />
<button onClick={showMes}>点击获取输入框内容</button>
</div>
)
}
- 类组件
// 类组件继承了React组件上的属性与方法,其中三大属性为 state props refs
class MyCmp extends React.Component {
render (){
return ( <h1>我是类组件</h1> )
}
}
ReactDOM.render(<MyCmp />,document.getElementById('root'))
4-2. 关于类组件的一些注意事项
- 组件中的render方法内的,this指向为组件实例
- 组件自定义方法中this为undefined,如何解决
方法1: 通过在constructor内,通过bind修改this的指向
方法2: 在constructor外,通过变量和箭头函数的方式,修改this指向 - 状态数据,不能直接修改或者更新, 如:this.state.xxx = xxx, 必须要使用 setState方法,如果修改数据为对象要创建新对象(object、array),详见 下面第7条
- 类组件 三大属性 state
- 什么情况下使用state, 类组件如果需要维护一个自身的状态,可以使用,函数组件没有这个属性
- 复杂的类组件可以使用, 简单的类组件不需要一个state
- state定义的位置
方式1:可以在constructor内定义,如:
class MyCmp extends React.Component {
constructor(props){
super(props)
this.state = { somedata: 100 }
}
render (){
let { somedata } = this.state;
return ( <h1>我是类组件 数据为 { somedata }</h1> )
}
}
方式2:在contructor外定义,如:
class MyCmp extends React.Component {
//contructor 不是必写
state = { somedata: 100 }
render (){
let { somedata } = this.state;
return ( <h1>我是类组件 数据为 { somedata }</h1> )
}
}
- 修改state,不能直接赋值,而是需要调用setState方法
- 通过事件,如onClick调用一个事件处理函数,来调用setState方法
- 关于事件处理函数放置的位置也存在两种
方式一,放在constructor内,值得注意的是需要修改this指向
constructor(props){
super(props) //写constructor必须写super(props)
this.eventHandle = this.eventHandle.bind(this);
}
eventHandle(){
dosomething...
}
方式二,放在constructor外面 写成变量赋值箭头函数的形式,this会因为箭头函数自动指向实例对象
constructor(props){
super(props)
...
}
eventHandle = () => {
dosomething...
}
- 关于setState的注意事项
**React遵循函数式编程规范。在函数式编程中,不推荐直接修改原始数据。 **
如果要改变或更改数据,则必须复制数据副本来更改。
所以,函数式编程中总是生成原始数据的转换副本,而不是直接更改原始数据。
纯函数的特点:
第一,同一个输入对应同一个输出
第二,函数内部不能使用不可预测的操作,如 网络请求、输入输出设备
第三,函数内部不能使用 Date.now() 与 Math.random()什么情况下,需要在组件内使用state?
某一个组件自己使用的,则state放在这个组件下
某一些组件用一个数据,则state放在它们共同的父级 (官方称之为状态提升
)要修改的state对象下,如果键名对应键值为一个字符串,可以直接通过setState修改
如:
class xxx extends React.Component {
state = { isActive: false }
...
mouseEnterFn = ()=> {
this.setState({
'isActive': true
})
}
}
- 要修改的state对象下,如果键名对应键值为一个对象(object,array),那么不能直接修改原来的数据,而要生成一个新的对象
- 原理:
react底层进行了一个浅拷贝,对比前后修改的对象是否是同一个对象(至于对象内的值是否改变无关紧要), 只关心引用地址值
如 数组的修改:
class xxx extends React.Component {
state = {
list:[]
}
changeList = ()=> {
let dataArr = this.state.list;
let NewList = [];
//方式一,通过concat返回一个新数组,而不能直接用push等方法直接在dataArr上操作
NewList = dataArr.concat([xxxx])
//方式二,通过展开运算符来返回新数组,并按照意愿调整新增数据的位置(插入第一个,或者插入最后一个)
NewList = [xxxx , ...dataArr]
NewList = [...dataArr , xxxx]
//方式三,通过数组map方法返回新数组,一般用于处理数据或者筛选数据
NewList = dataArr.map((item)=>{ return item*2; })
NewList = dataArr.map((item)=>{ if(item%2==0){ return item; } })
//方式四,通过filter来返回新数组
//注意:filter() 方法创建一个新的数组; filter() 不会对空数组进行检测; filter() 不会改变原始数组;
NewList = dataArr.filter((item)=>{ return item%2==0; })
//方式五,forEach遍历原来数组,根据条件,将筛选项插入新数组内
dataArr.forEach((item)=>{ if(item%2==0){ NewList.push(item); } })
this.setState({
list: NewList
})
}
}
- 要修改的state对象下,如果键名对应键值为一个对象(object,array),那么不能直接修改原来的数据,而要生成一个新的对象
如 对象的修改:
class xxx extends React.Component {
state = {
obj: { a:1, b:2 }
}
changeList = ()=> {
let dataObj = this.state.obj;
//方式一,通过assign返回一个新的对象( 属于浅拷贝 ),并修改对象内的数据
dataObj = Object.assign({ },dataObj,{a:10,b:20})
//方式二,展开运算符拷贝和修改数据
dataObj = { ...dataObj, a:10 }
this.setState({
obj: NewObj
})
}
}
- setState是异步的,设置state是同步操作,但是渲染是异步操作
通过setState的第二个参数可以获取最新的state数值,这个函数会在页面render后触发
import React, { Component } from 'react'
class Count extends Component {
state = {
num:0
}
addNum = () =>{
var oldNum = this.state.num;
this.setState({
num: oldNum+1
},function(){
console.log(this.state.num); //1
})
console.log(this.state.num); //0
}
}
- setState不仅仅可以接收对象,也可以接收函数
setState函数形式的格式: this.setState(( oldState )=>{ return xxxx })
此外这种写法也支持第二个参数,即render后的callback函数
对象形式写法是函数形式写法的语法糖
//-- 对象形式
this.setState({
num: this.state.num+1
});
//-- 函数形式 (最终函数return出来的依然是一个对象)
this.setState((oldstate)=>{
return { num: oldstate.num+1 };
},function(){
console.log(this.state.num);
})
写法推荐的建议:
如果不需要依赖之前的值,优先写对象形式
如果依赖之前的值,可以写函数形式,这样之前的值就不用再保存到一个变量内了,直接从函数参数里拿即可
- 类组件 三大属性 props
- props - 通过组件标签挂载的属性,使得父组件可以将数据或者方法传递至子组件中
子组件通过 this.props 来获取父级组件的属性以及方法 - 组件之间通信的方式之一
- 函数类型组件也是可以获取到props的
function Person(props){
console.log(props)
return <h6>hello world 类型{typeof props.myname} - {props.myname}</h6>
}
ReactDOM.render(
<Person myname={100} />,
document.getElementById('root')
);
- react-router-dom,操作路由 其中
this.props.match.params -- params 风格理由配置
this.props.history.search -- query 风格 路由配置
this.props.history.state -- state 风格 理由配置
都是挂载在props属性上面
- 类组件 三大属性 refs
用来获取元素节点
一共有四种方式来获取元素节点
- 方式一:
字符串类型获取 (存在效率问题,未来可能会被移除,官方不建议使用)
class xxx extends React.Component {
doSearch = ()=> {
let _searchInput = this.refs.searchInput;
console.log(_searchInput.value);
}
render (){
return <input type="text" ref="searchInput" placeholder="输入搜索内容" />
}
}
- 方式二:
ref等于函数,在标签内写行内函数,函数参数为当前DOM节点,通过箭头函数,将this指向组件实例
class xxx extends React.Component {
doSearch = ()=> {
let _searchInput = this.searchInput;
console.log(_searchInput.value);
}
render (){
return <input type="text" ref={currentNode=>{ this.searchInput = currentNode}} placeholder="输入搜索内容" />
}
}
- 方式三:
ref等于函数,不将函数写在行内,而是在组件内定义,函数的参数为操作的DOM节点
class xxx extends React.Component {
doSearch = (currentNode)=> {
this.searchInput = currentNode;
console.log(this.searchInput.value);
}
render (){
return <input type="text" ref={this.doSearch} onKeyUp={this.doSearch} placeholder="输入搜索内容" />
}
}
※ 相较于行内方式,更新数据会被调用两次,第一次用于重置ref标签数据为null,第二次为正常获取DOM节点
※ 官方的描述,这个属于正常现象,写内联箭头函数,或者是调用类组件内的函数,都属于正常现象
※ 标签内写箭头函数还是一种主流方式
- 方式四:
※ 通过类上挂载属性,通过React.createRef聊创建容器,用来储存ref的元素节点
※ 容器为元素一一对应,不能混用
※ 流程:创建ref数据容器,元素标签上ref绑定这个容器,
class xxx extends React.Component {
searchInputRef = React.createRef()
doSearch = ()=> {
console.log(this.searchInputRef.current.value);
}
render (){
return <input type="text" ref={ this.searchInputRef} onKeyUp={this.doSearch} placeholder="输入搜索内容" />
}
}
- 绑定事件的一些注意点
- 更好的兼容性:事件是react合成的自定义事件,为了更加好的浏览器兼容性,例如: onclick -> onClick
- 更加高效:react通过事件委托的方式,将绑定事件元素,提升到父级容器
- 不能过渡使用ref,如果可能,能不用就不用
例如:
获取表单的数据,不要使用ref获取表单元素节点,而是通过事件的event对象,获取表单内容
受控组件 -> 非受控组件
class xxx extends React.Component {
doSearch = (event)=> {
console.log(event.target.value);
}
render (){
return <input type="text" onKeyUp={this.doSearch} placeholder="输入搜索内容" />
}
}
- 关于事件处理函数传参的问题
原理:
※ 通过函数柯里化,或者说是高阶函数,一个函数执行后返回另一个函数,来实现参数传递
react函数如果带参数,会立即执行,这个不是react要的,它需要一个函数,
因此借助高阶函数来实现函数传参
※ 常见的高阶函数有:promise、setTimeout、array.map等
方式一,标签内部自执行一个匿名函数,并将参数event,传递至下一个函数
class xxx extends React.Component {
doSearch = (event,data)=> {
console.log(event.target.value);
console.log(data); //somedata....
}
render (){
return <input type="text" onKeyUp={(event)=>{ this.doSearch(event,'somedata....') }} placeholder="输入搜索内容" />
}
}
方式二,正常调用函数,在函数内部,通过箭头函数自执行,返回对应的event
class xxx extends React.Component {
doSearch = (data)=> {
return (event)=>{
console.log(event.target.value);
console.log(data); //somedata....
}
}
render (){
return <input type="text" onKeyUp={ this.doSearch('somedata....') } placeholder="输入搜索内容" />
}
}
- 受控组件与非受控组件
简而言之就是输入框,没有绑定onChange或onInput、onKeyUp等类似控制
以复选框为例
<input type="checkbox" checked={true} />
//如果没有绑定事件onChange存在,那么它将不能改变
//react会建议使用defaultChecked 代替 checked,但它仅会在页面渲染的第一次受到数据影响
//Warning: You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.
改写为
<input type="checkbox" checked={this.state.inputcheck} onChange={this.changeFn} />
-
生命周期
- 一些概念:
挂载组件(mount),卸载组件(unmount) - 生命周期调用次数:
componentDidMount - 组件挂载,调用1次
componentWillUnmount - 组件更新,调用N次 - 一个组件开启定时器与关闭定时器的时机
constructor(props){
super(props)
this.state = { ... }
}
componentWillMount(){
//组件将要挂载....
}
render(){
return (
<div>
...
</div>
)
}
componentDidMount(){
this.iTmer = setInterval(()=>{ ... },1000);
}
componentWillUnmount(){
clearInterval(this.iTimer);
}
shouldComponentUpdate(){
//需要返回布尔值,组件是否允许更新
return true
}
componentWillUpdate(){
}
componentDidUpdate(preProps,preState){
//preProps 更新之前的props
//preState 更新之前的state
}
13-1. react新旧版本下,生命周期的差异
- 即将废弃:
componentWillMount -> 使用需要加上 UNSAFE_ 前缀
componentWillReceiveProps -> 使用需要加上 UNSAFE_ 前缀
componentWillUpdate -> 使用需要加上 UNSAFE_ 前缀
- 新增:
getDerivedStateFromProps
-> 静态方法 前面需要加上 static
-> 通过组件标签挂载属性,在这个生命周期参数可以获取,返回值可以左右组件内的state
-> 必须有返回值,可以返回null
-> 会导致代码冗余、会使组件难以维护
-> 若state值都取决于props,可以使用这个生命周期
getSnapshotBeforeUpdate
-> 可以带两个参数 prevProps, prevState
-> 必须有返回值,可以为null; snapshot值可以为任意,会传递至 componentDidUpdate
-> 会将至传递给 componentDidUpdate的第三个参数
-> 使用场景极其罕见
- 虚拟DOM与DIFF
※ key的意义?
react数据更新 新的虚拟DOM与旧的虚拟DOM,通过DIFF算法进行比对,针对变化的地方进行更新,提高DOM复用
有key的时候,diff会优先,对相同结构树下同key结构进行比对
如果没有key,判断是新元素,会重新创建DOM元素,降低性能与复用率
※ 使用索引作为key有什么后果?
- 如果单纯的展示,且新插入数据为正序排序(新插入数据在后面),没有影响排序的行为,则没有问题
- 如果新数据插入在列表最前面,会影响所有列表项,所有的元素都会重新创建,且可能造成布局错乱
※ 常规做法:
- 使用唯一id作为key,而不是索引
- 使用三方包,如 nanoid,来生成唯一的key
npm install nanoid -S
import {nanoid} from 'nanoid'
//返回为函数,调用即可
console.log(nanoid());
- 组件之间通信手段
※ 方式一:props传递函数
父组件通过向子组件上挂载方法,子组件通过props来获取方法,并调用函数,将传递数据作为参数传递至父组件
父组件:
fatherFn = (sonData)=> {
console.log('子组件数据:', sonData); //123
}
render() {
return (
<div className="warp">
{/* 顶部 一般组件 */}
<Head fatherFn={this.fatherFn} />
</div>
)
}
子组件:
render(){
let {fatherFn} = this.props;
return (
<div className="headBox">
<div>
{/* 注意传参要包裹一个自执行匿名箭头函数,否则返回的不是函数,因为react会默认调用带()的函数 */}
<button onClick={()=>{ fatherFn('123') }}>通信</button>
</div>
</div>
)
}
※ 方式二:第三方包 'pubsub-js' ,通过发布与订阅来实现任意位置组件之间通信
安装
npm install pubsub-js -S
组件订阅(位置:生命周期的组件挂载)
componentDidMount(){
//订阅信息
this.subscribeObj1 = PubSub.subscribe('mydata', function(msg, data){
console.log( msg, data );
// msg - 'mydata' , data - '发布的一段信息...'
});
}
组件解绑订阅(位置:生命周期的组件卸载)
componentWillUnmount(){
//取消订阅
PubSub.unsubscribe(this.subscribeObj1);
}
组件发布 位置
//发布一个信息
pubFn = ()=>{
PubSub.publish('mydata', '发布的一段信息...');
}
render(){
return (
<div>
{/* 发布一个信息 */}
<button onClick={this.pubFn}>发布信息</button>
</div>
)
}
※ 方式三:redux、react-redux 管理数据共享
redux 第三方数据状态管理
react-redux 官方出品,与第三方包redux 搭配使用
见 (下面 ↓) redux与react-redux部分
※ 方式四:通过React.createContext来创建上下文
这个目前学习感觉像一种单向的数据通信,方便祖辈传递信息和同步信息至子孙组件
第一步,创建一个Context文件夹用于创建上下文的文件。
import React from 'react'
//这个是一个上下文的对象,名称为'MyContext'
export const MyContext = React.createContext();
export const { Provider,Consumer } = MyContext;
第二步,在祖先组件中使用Provider包裹需要祖先数据的组件
import { Provider } from './components/Context'
传递数据至Head组件
<Provider value={this.state.studentInfor}>
<Head addItemFn={this.addItemFn} />
</Provider>
特别注意:
- Provider上的value属性名称,是固定的不能修改,否则报错
Did you misspell it or forget to pass it?
第三步,方法1 - 在后代组件中获取传递的数据
import { Consumer } from './components/Context'
export default function Funcmp3() {
return(
<p>
<Consumer>
{
(value999999)=>{
return `姓名:${value999999.name} - ${value999999.age}`
}
}
</Consumer>
</p>
)
}
注:
- 在Consumer内的参数可以自行定义,不用担心报错,如上‘value999999’
- 使用Consumer既可以在函数式组件,也可以在类组件内使用
第三步,方法2 - 仅子组件为类组件时可以使用
//引入上下文对象
import { MyContext} from '../Context'
export default class Footer extends Component {
//表名该组件需要通过上下文接收数据
static contextType = MyContext
render(){
return (
<div>
<p>姓名:{this.context.name} - {this.context.age}</p>
</div>
)
}
}
注:
- 类组件内 添加静态属性,标明需要接收上下文数据 static contextType = MyContext
- 在实例的context上获取传递的数据
react-cli
react 快速开发脚手架,包含 语法检查、jsx编译、devserver热更新等..
- 安装脚手架
npm install create-react-app -g
- 创建项目
create-react-app 项目名称
- 启动项目
npm start
- 模块化引用样式,防止样式污染
//index.css -> index.module.css --- 固定的格式变化
import css1 from './index.module.css'
export default class MyCmp extends Component {
render(){
return (
<div><h2 className={css1.tit}>标题</h2></div>
)
}
}
- 关于调试项目,如何使用代理,调用远程服务器接口
※ 方式一:
在package.json中添加配置
"proxy": "http://localhost:5000" // 一个远程服务器的地址,与你的本地服务器存在跨域
※ 方式二:
在src文件夹内,创建一个新文件 setupProxy.js
//内置包,无需安装
const proxy = require('http-proxy-middleware');
module.exports = function(app){
app.use(
proxy('/api',{ //规则关键词,如果存在接口 /api/list
target:'http://localhost:5000', //请求后台服务器接口的地址
changeOrigin: true, //是否伪装地址,如果不伪装则为当前静态页面的地址,否则为 上面请求服务器的地址
pathRewrite: {'^/api':''} //请求服务器接口会替换为 http://localhost:5000/list
})
)
}
上面代理配置完了,前端页面请求数据
getDetalFn = (id)=> {
//请求数据
let that = this;
//请求地址不能写远程地址,要写本地服务器地址
//真实服务器接收到数据的request.path为 http://localhost:5000/detail/xxx ,将规则标识api替换为空
axios.get(`http://localhost:3000/api/detail/${id}`)
.then(function (response) {
let _cont = response.data;
that.setState({
'cont':_cont
})
})
.catch(function (error) {
// handle error
console.log(error);
})
}
相对于之家在package.json内配置代理,可以配置多个代理,规定代理的触发关键词
react-router
安装
npm install react-router-dom -S
概念
- 单页面应用 (SPA - single page web application)
- 这个页面切换hash或者切换history(H5),来调用不同的组件,渲染更新页面上的不同区域
- 数据通过ajax请求获取,不刷新页面
- 路由的理解
- 路由是路径与组件的一个映射关系
- 路由的分类
- 后端路由
router.get(path,function(req,res){ ... }) - 前端路由
react中,切换path显示component, <Route path="/list" component={ List } />
- 路由内的组件
<Link /> - 基础的路由按钮
<NavLink /> - 可以有激活状态的路由按钮,默认有一个active 类名,可以通过activeClassName配置
<Route /> - 路由配置,映射理由与组件的关系
<Switch></Switch> - 匹配第一个相符的路由映射,对于后面的不再匹配筛选
<Redirect /> - 重定向,放在路由配置的最下面,作为逻辑兜底
<BrowserRouter> - history风格路由
<HashRouter> - hash路由
4-2. BrowserRouter与HashRouter的区别:
底层原理:
BrowserRouter 是H5的history API,不兼容IE9及以下 ( 提出业务需求时候,明确低版本浏览器不支持 )
HashRouter 是使用URL的哈希值 (#后的部分,不会发送至服务器)path表现形式不一样
BrowserRouter 是 localhost:3000/test
HashRouter 是 localhost:3000/#/test浏览器刷新
BrowserRouter 没有任何影响,因为state存在history内
HashRouter 刷新导致state参数丢失HashRouter 可以解决引用静态文件路径错误问题
建议:
优先考虑 BrowserRouter (不考虑浏览器兼容的情况下)
如果使用 HashRouter 会造成state方式配置路由错误,因为没有底层history记录state数据,刷新为undefined,可以使用params或search风格路由
- 配置路由
- 路由的设置
import {NavLink,Link,Route,Switch} from 'react-router-dom';
import About from './compontents/About'
import News from './compontents/News'
<NavLink className="btn" activeClassName="myactive" to="/about">关于</NavLink>
<NavLink className="btn" activeClassName="myactive" to="/news">新闻</NavLink>
<Switch>
{/* About */}
<Route path="/about" component={About} />
{/* News */}
<Route path="/news" component={News} />
{/* 默认路径,兜底 */}
<Redirect to="/about" />
</Switch>
- 路由方式的设置,在入口文件中
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom'
import App from './App';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
- 路由嵌套
- 路由的匹配原则,优先匹配第一层路由,然后逐层匹配
{/* 二级路由 */}
<div className="sonMenu">
<NavLink className="sonBtn" activeClassName="sonBtnAc" to="/news/hot">热点新闻</NavLink>
<NavLink className="sonBtn" activeClassName="sonBtnAc" to="/news/today">今日新闻</NavLink>
</div>
{/* 呈现区域 */}
<div className="sonCont">
<Switch>
<Route path="/news/hot" component={Hot} />
<Route path="/news/today" component={Today} />
<Redirect to="news/hot" />
</Switch>
</div>
- 路由的细节
- 严格模式(精准匹配)
会影响路由嵌套
<Route exact={true} path="/home" component="xxx" />
path可以被to包含,但是不能超过,否组件将无法被渲染
可以渲染组件的情况:
<Link to="/home/a/b/c">home页面</Link>
<Route path="/home" component="xxx" />
- 不能渲染组件的情况
<Link to="/home">home页面</Link>
<Route path="/home/a/b/c" component="xxx" />
结论: to可以包含path,且必须开头匹配,如果不匹配就失败了,这个方式属于模糊匹配
- 路由传参
※ 方式一 search方式
调用:
<Link to="/news/today/?id=123">
配置:
<Route path="/news/today" component={Detail} />
//--编程式导航
this.props.history.push({ path:'/news/today',query:{id:123} });
获取:
this.props.location.search
注:需要借助于querystring来解析地址,删除首字母?,并使用querystring.parse()来将字符串转化为对象
※ 方式二 params方式
调用:
<Link to="/news/today/123">
配置:
<Route path="/news/today/:id" component={Detail} />
//--编程式导航
this.props.history.push( '/news/today/123' );
获取:
this.props.match.params
※ 方式三 state方式
调用:
<Link to={ {path:'/news/today',state:{id:123}} }>
配置:
<Route path="/news/today" component={Detail} />
//--编程式导航
this.props.history.push({ path:'/news/today',state:{id:123} });
获取:
this.props.location.state
注:刷新也可以保留参数,但是如果清空浏览器缓存,state会消失,需要考虑调用undefined下属性的情况
7-1. 路由组件与一般组件
- 路由组件不是自己通过标签写入页面,而是通过路由配置,让路由自己去调用
- 一般组件需要自己去引用组件,添加到页面,无法调用this.props.history等
- 一般组件转化为路由组件,通过withRouter包裹即可
import {withRouter} from 'react-router-dom'
class Head extends Component {
...
}
export default withRouter(Head);
- 使用params进行路由传参时候,会遇到一个情况,就是无限调用render
情景:
列表菜单与列表详情在同一个页面情况下,
根据params传递参数ID,componentDidMount来请求服务器,获取数据,然后进行setState操作,
如果点击列表菜单,再次进行操作,无限陷入render情况
解决:
componentDidUpdate生命周期,进行前后params的id比对,不同情况下,再次进行数据请求
componentDidUpdate(prevProps, provState, snapshot){
let {id} = this.props.match.params;
if(prevProps.match.params.id!==this.props.match.params.id){
this.getDetalFn(id);
};
}
原因:
componentDidMount调用函数中setState触发了更新componentDidUpdate,更新componentDidUpdate调用setState调用函数中setState又触发了componentDidUpdate,进而componentDidUpdate无限自己调用自己
- 封装一个NavLink,将常用的属性封装组件内,放置反复写
- 定义:
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
import './index.css'
{/* 注意 {...this.props} 放在默认属性的右侧,这样自定义属性与默认属性同时存在,自定义会覆盖默认*/}
export default class MyNavLink extends Component {
render() {
return (
<NavLink className="btn" activeClassName="myactive" {...this.props} />
)
}
}
- 调用:
<MyNavLink to="/news">新闻</MyNavLink> //使用默认的属性
<MyNavLink className="sonBtn" activeClassName="sonBtnAc" to="/news/hot">热点新闻</MyNavLink> //使用部分自定义属性
- 注意:
MyNavLink组件在定义时候,标签体内容在this.props.children内( 标签体内容为特殊的属性 ),所以一并与其他属性一起解构即可
redux
安装 npm install redux -S
- 概述:
- 状态管理的JS库,非react官方
- 可以配合前端三大框架使用,但基本与react配合使用
- 集中管理react应用中多个组件共享的状态
- 使用场景:
- 某个组件状态与其他组件一起用
- 一个组件需要改变另外一个组件的状态
- 使用原则,能不用就不用,非用不可才用
- 目录结构建议
同components级目录下,创建redux文件夹,里面放置
- store.js
- action 文件夹
- reducer 文件夹
- type类型的常量.js (可有可无,方便多文件内修改action的type,放置输入错误的情况)
-
原理图
- Action Creators 创建函数返回一个对象,对象的格式为
// +data操作的action
export const addAction = (data)=>{
return {
type: 'add',
data: data*1
}
}
// -data操作的action
export const redAction = (data)=>{
return {
type: 'redu',
data: data*1
}
}
- Reducer
//初始值
let initState = 0;
export default function reduceFn(preState=initState,action){
switch(action.type){
case 'add':
return preState+action.data;
case 'redu':
return preState-action.data;
default:
return preState;
}
};
- Store 创建一个数据仓库 (返回store)
import {createStore} from 'redux'
import countReducer from './count_reducer'
export default createStore(countReducer)
- Store 派发action
//-- 派发一个+10的操作
store.dispatch(addAction(10));
//-- 派发一个减20的操作
store.dispatch(redAction(20));
- Store 内的数据变化时候,强制调用render更新视图 ( setState一个空对象 )
store.subscribe(function(){
that.setState({}); //强制触发render视图(当store内的数据变化时候)
})
也可以对整个App来做监听状态变化的刷新(index.js)
ReactDOM.render(<App />,document.getElementById('root'));
//监听状态变化后,刷新App
store.subscribe(()=>{
ReactDOM.render(<App />,document.getElementById('root'));
});
- 获取Store内的数据
store.getState(xxx); // store获取数据
- redux库的基本构成
Action Creator - 接收组件请求,创建一个动作,将需要做的事儿,dispatch给Store
Store - 将Action Creator的要求发送(之前的状态以及ActionCreator传递的事儿)给 Reducers
Reducers - 处理数据,返回新的数据(初始化数据(之前的状态为undefined),与更新数据)
React Component - 通过getState从Store请求获取新数据,以及发送请求给 Action Creator
React Component 相当于客户
Action Creator 相当于服务员 初始化时候 dispatch(action) action的{type:@@init@@} data不传递,只传递type
Store 相当于餐厅经理
Reducers 相当于厨师
客户 -> 服务员 -> 经理 -> 厨师 -> 经理 -> 客户
- 关于异步action如果不像放在组件内,而想放在交给action中
由于store必须dispatch一个普通对象,而非函数,所以需要借助一个中间件。
需要借助于第三方库 redux-thunk
安装 npm install redux-thunk
步骤1,action创建函数不返回对象,返回一个函数,函数内容部调用同步action
步骤2,store中通过applyMiddleware包裹thunk(三方包)来作为createStore的第二个参数
步骤3,在组件事件处理函数中,直接用store派发action, 如:store.dispatch(addActionAsync(数据,延迟时间))
store.js ( 通过applyMiddleware载入中间件 )
import {createStore,applyMiddleware} from 'redux'
import countReducer from './count_reducer'
import thunk from 'redux-thunk'
export default createStore(countReducer,applyMiddleware(thunk))
- 异步action
/*
* 根据不同逻辑,返回一个action对象 ,或者一个异步函数(需要借助于三方库 redux-thunk)
* 对象 { type:'xxx', data:'xxxx' }
* 函数 function(){ return action调用 }
*/
import {ADD,RED} from './const_vars'
import store from './store'
export const addAction = (data)=>{
return {
type: ADD,
data: data*1
}
}
export const redAction = (data)=>{
return {
type: RED,
data: data*1
}
}
export const addActionAsync = (data,time)=>{
return ()=>{
setTimeout(()=>{
store.dispatch(addAction(data));
},time)
}
}
- 组件内
import React, { Component } from 'react'
import store from '../../redux/store'
import {addAction,redAction,addActionAsync} from '../../redux/count_action'
export default class Count extends Component {
// 事件处理函数
dealFn = (typeName)=> {
let _val = this.selectEle.value;
if(typeName===ADD){
store.dispatch(addAction(_val));
}else if(typeName===RED){
store.dispatch(redAction(_val));
}else if(typeName===ADDIFEVEN){
let nowVal = store.getState();
if(nowVal%2!==0){
store.dispatch(addAction(_val));
}
}else if(typeName===ADDASYNC){
//一个借助于redux-thunk的异步action
store.dispatch(addActionAsync(_val,1000))
}
}
render() {
return (
<div>
<h3>计算后的值:{store.getState()}</h3>
<div>
<select ref={(c)=>{ this.selectEle = c }}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
{/* 简化 */}
<button onClick={()=>{ this.dealFn('add') }}>加</button>
<button onClick={()=>{ this.dealFn('red') }}>减</button>
<button onClick={()=>{ this.dealFn('addIfEven') }}>奇数加</button>
<button onClick={()=>{ this.dealFn('addAsync') }}>异步加</button>
</div>
</div>
)
}
}
react-redux
- 特点
- react-redux官方提供,目的是减少对store频繁直接调用,如 store.getState、store.dispatch、store.subscribe,等一些列操作都放在container容器上,通过容器来做这一些列操作
- 从react-redux中导入connect,将UI组件,state、dispatch方法导入到container容器
- 将store创立的仓库,挂载到容器标签上
- UI组件内的数据通过属性获取(mapStateToProps),修改状态的方法(mapDispatchToProps)同样通过属性获取
- store与reducer的使用,同使用redux没有什么差别,主要工作在UI组件内不停的调用属性
原理图:
总结点:
- 使用流程
第一步,安装react-redux, 同component文件夹,创建一个容器文件夹,创建一个容器文件 如:Count.js
//引入Connect
import { connect } from 'react-redux'
//引入UI
import CountUI from '../components/Count'
//引入Action
import { addAction,redAction,addActionAsync } from '../redux/count_action'
//mapStateToProps 返回对象
function mapStateToProps(state){
return {count:state}
}
//mapDispatchToProps 返回对象,对象内是方法
function mapDispatchToProps(dispatch){
return {
ADDFn: (val)=>{ dispatch(addAction(val)) },
REDFn: (val)=>{ dispatch(redAction(val)) },
ADDASYNC: (val,time)=>{ dispatch(addActionAsync(val,time)) }
}
}
export default connect(mapStateToProps,mapDispatchToProps)(CountUI);
第二步,在UI组件内使用,通过属性props传递过来的属性 state 与 dispatch
import React, { Component } from 'react'
import {ADD,RED,ADDIFEVEN,ADDASYNC} from '../../redux/const_vars'
export default class Count extends Component {
dealFn = (typeName)=> {
let _val = this.selectEle.value;
if(typeName===ADD){
//通过属性获取修改状态的方法
this.props.ADDFn(_val)
}else if(typeName===RED){
this.props.REDFn(_val)
}else if(typeName===ADDIFEVEN){
let nowVal = this.props.count;
if(nowVal%2!==0){
this.props.ADDFn(_val)
}
}else if(typeName===ADDASYNC){
this.props.ADDASYNC(_val,1000);
}
};
render() {
return (
<div>
{/*通过属性获取状态*/}
<h3>计算后的值:{this.props.count}</h3>
<div>
<select ref={(c)=>{ this.selectEle = c }}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
{/* 简化 */}
<button onClick={()=>{ this.dealFn('add') }}>加</button>
<button onClick={()=>{ this.dealFn('red') }}>减</button>
<button onClick={()=>{ this.dealFn('addIfEven') }}>奇数加</button>
<button onClick={()=>{ this.dealFn('addAsync') }}>异步加</button>
</div>
</div>
)
}
}
- 优化容器组件的书写方式
//引入Connect
import { connect } from 'react-redux'
//引入UI
import CountUI from '../components/Count'
//引入Action
import { addAction,redAction,addActionAsync } from '../redux/count_action'
/*
* 状态对应一个对象 ( count的命名,可以根据reducer内的处理来语义化命名 )
* 修改状态方法也对应一个对象 ( 返回对应创建action的方法即可,内部的逻辑由react-redux来处理 )
*/
export default connect(
state => ({count: state}),
{
ADDFn: addAction,
REDFn: redAction,
ADDASYNC: addActionAsync
}
)(CountUI);
- 优化容器的store挂载
将所有容器组件上挂载的store,都统一挂载到react-redux的Provider组件上,用Provider包裹App组件,以实现一次性完成对所有容器组件的store挂载
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
- 将容器组件与UI组件整合到一起使用
import React, { Component } from 'react'
//引入Connect
import { connect } from 'react-redux'
//引入Action
import { addAction } from '../redux/count_action'
class CountUI extends Component {
// 简化
dealFn = (typeName)=> {
let _val = this.selectEle.value;
if(typeName==='add'){
this.props.ADDFn(_val)
}
}
render() {
return (
<div>
<h3>计算后的值:{this.props.count}</h3>
<div>
<select ref={(c)=>{ this.selectEle = c }}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
{/* 简化 */}
<button onClick={()=>{ this.dealFn('add') }}>加</button>
</div>
</div>
)
}
};
// -- 简写方式
export default connect(
state => ({count: state}),
{
ADDFn: addAction
}
)(CountUI);
-
react-redux书写流程整理
- 多个store数据
使用combineReducers可以将多个reducer整理一起,用一个key:value的形式保存
import {createStore,applyMiddleware,combineReducers } from 'redux'
import thunk from 'redux-thunk'
import countReducer from './reducers/count'
import personReducer from './reducers/person'
/*reducers列表*/
let allReducer = combineReducers({
num: countReducer,
person: personReducer
})
export default createStore(allReducer,applyMiddleware(thunk))
在容器组件内,同过key值来获取每个reducer的状态值
如:
// -- 简写方式
export default connect(
state => ({
num: state.num,
person: state.person
}),
{ ...改变状态的action的key、value对儿 }
)(CountUI);
优化以及细节工作
- 组件按需加载 ( Lazy,Suspense都来自与react核心库,无需二次安装 )
import React, { Component,Lazy,Suspense } from 'react'
import { Switch,Route } from 'react-router-dom'
//按需引入组件
const About = Lazy(()=>{ import('@/component/About') });
//配置路由
<Suspense fallback={<h1>LOADING....</h1>}>
<Switch>
<Route path="/about" component={About }>
</Switch>
</Suspense>
- 由于React的jsx语法限制,不管是类组件还是函数式组件,返回的结构只能有一个根节点,而且这个节点会被渲染到页面中,无形中添加了很多无意义的div标签
解决方法
使用Fragment组件来替代一般html标签
import React,{Fragment} from 'react'
export default function Funcmp2(props) {
return (
{/* 这个类名为aaa的标签,不会被渲染到页面上 */}
<Fragment className='aaa'>
<h1>这是一个函数自增长的函数类型组件 {num}</h1>
<p>通过React.useEffect来开启一个定时器 </p>
<button onClick={delFn}>删除这个组件</button>
</Fragment>
)
};
额外说明:
- 可以使用<> xx </>代替Fragment
- Fragment组件上,只能添加一个key属性,否则会报错
Invalid prop "name" supplied to "React.Fragment". React.Fragment can only have "key" and "children" props.
- 减少不必要的组件render
问题1: 父子组件嵌套,子组件没有引用父组件要修改的数据,但是父组件setState改变数据,会触发render,连带子组件也都一并被render了,因为组件的shouldComponentUpdate默认返回的布尔值为true
问题2: this.setState({}); 设置一个空对象,也会触发render
解决思路:
- 通过shouldComponentUpdate的参数nextProp、nextState与组件之前的状态比较,只有发生变化才返回true,允许更新组件,反之返回false
父组件:
export default class Cmp1 extends Component {
....
render() {
return (
<div style={{backgroundColor:'green',padding:'10px'}}>
<h1>我是组件1</h1>
<button onClick={this.addNumFn}>点击我加1</button>
<button onClick={this.changeNameFn}>点击我改名</button>
<div>
<Cmp2 num={this.state.num} />
</div>
</div>
)
}
}
子组件:
import React, { Component } from 'react'
export default class Cmp2 extends Component {
shouldComponentUpdate(nextProp,nextState){
//使用父级的state有变化
if(nextProp.num !== this.props.num){
return true;
}else{
return false;
}
}
render() {
let { num } = this.props;
//如果父级改变的数据,不是Cmp2使用的数据,为了优化,子组件可以不用跟随父组件一起render
return (
<div style={{backgroundColor:'#ccc',padding:'8px',marginTop:'8px'}}>
<h6>我是组件2</h6>
<p>{num}</p>
</div>
)
}
}
- 通过React的内置组件PrueComponent来自动进行prop与state变化比较
使用PureComponent的例子
子组件:
import React, { PureComponent } from 'react'
export default class Cmp2 extends PureComponent {
...
render() {
let { num } = this.props;
//如果父级改变的数据,不是Cmp2使用的数据,为了优化,子组件可以不用跟随父组件一起render
return (
<div style={{backgroundColor:'#ccc',padding:'8px',marginTop:'8px'}}>
<h6>我是组件2</h6>
<p>{num}</p>
</div>
)
}
}
额外注意的:
第一,PureComponent自己一个封装shouldComponentUpdate;
如果PureCompnent里书写shouldComponentUpdate会报错
shouldComponentUpdate should not be used when extending React.PureComponent.
Please extend React.Component if shouldComponentUpdate is used.
第二,修改state使用setState时候,需要修改一个新对象,而不能修改对象上的属性,否则React浅比较,不会对同一个对象做render
//数据
state = { name:'张三' }
changeName = () => {
let obj = this.state;
//react不会render页面,因为obj是原来的对象,引用的内存地址是同一个
obj.name='李四';
this.setState(obj);
//这个会render,因为setState里是一个全新的对象
this.setState({
name: '李四'
});
};
第三,设置state为空,react将不会调用render方法
this.setState({}) //设置空对象的方法,将不会触发render,在PureComponent组件中
- 不用PureComponet的情况下,依然来手动判断,什么情况下更新组件
在React17开始,在使用setState时候,用函数式书写方式,返回一个null,react将不会触发render
//在事件处理函数内,对setState进行二次逻辑判断,数值没有变化返回null,反之 正常返回
changeName = (ev) => {
let {name} = this.state;
let changeName = ev.target.value;
this.seState(()=>{
if(name===changeName ){
return null;
}else{
return { name: changeName }
}
})
}
- render props 组件插槽
- 父组件通过挂载属性,等于一个函数,函数的返回值为一个组件,通过函数传参可以将父组件的数据传递至子组件,而父组件内需要预留一个位置,放这个未知的组件,这个未知的地方就是插槽的地方
- 类似Vue在组件标签体内写的内容,会出现组件内部的slot内,react的标签体就是一个自定义属性,而在组件标签内只需调用该属性对应函数,将组件内的数据作为该函数的参数即可
- 一般为了阅读快速理解,不成文规定大家将返回组件的自定义属性名,设置为'render'
父组件:
import React, { Component } from 'react'
import Cmp1 from './components/Cmp1'
import Cmp2 from './components/Cmp2'
export default class App extends Component {
render() {
return (
<div>
<Cmp1 render={(num)=>{ return <Cmp2 num={num} /> }} />
</div>
)
}
}
Cmp1组件:
export default class Cmp1 extends Component {
state = {
num:1,
name:'张三'
}
render() {
return (
//子组件在Cmp1中希望放置的位置
<h1>我是组件Cmp1</h1>
<div id="box">
{this.props.render(this.state.num)}
</div>
)
}
Cmp2组件:
export default class Cmp2 extends PureComponent {
render() {
let { num } = this.props;
return (
<div style={{backgroundColor:'#ccc',padding:'8px',marginTop:'8px'}}>
<h6>我是组件Cmp2</h6>
<p>{num}</p>
</div>
)
}
}
- 组件渲染降级,getDerivedStateFromError
- getDerivedStateFromError 是静态属性
对因为不确定因素无法渲染的子组件,进行降级渲染,防止整个组件嵌套体系全部因为改子组件错误,而无法渲染界面 - getDerivedStateFromError 在生产环境下,按照我们预期会渲染一下,然后依旧报错,这个在生产环境下会正常
- componentDidCatch 生命周期,可以帮助我们汇总错误,发送服务器后台
export default class Cmp1 extends Component {
state = {
list: [ xxx,xxx ]
hasError: false
}
static getDerivedStateFromError(){
//改组件嵌套的子组件存在错误,因此通过开关,阻止子组件进一步渲染
return { 'hasError': true };
}
componentDidCatch(error, info){
//将错误信息发送至后台服务器,用于记录错误日志,便于开发人员汇总与修改
console.log('info ~~~ ');
console.log(typeof info.componentStack); //string
}
render() {
if (this.state.hasError) {
// 你可以渲染任何自定义的降级 UI
return <h1>Something went wrong.</h1>;
}
return <Cmp2 data={this.state.list} />;
}
}
花式报错整合
- 给一个不存在的组件设置数据
移除了一个组件,或者切换路由,之前的组件开启定时器或者异步数据请求,且在恰当的时候,类组件调用了setState(函数式组件调用了React.useState的设置state的方法)
The node you're attempting to unmount was rendered by React and is not a top-level container. Instead, have the parent component update its state and rerender in order to remove this component
解决方法:
在类组件compontentWillUnmount(函数式组件 React.useEffect第一个函数参数回调的函数内)
- 针对定时器,停止定时器
函数式组件关闭定时器:
let [num,addNum] = React.useState(0)
React.useEffect(()=>{
let iTimer = setInterval(function(){
addNum((num)=>{
return num+1
})
},1000);
return (()=>{
clearInterval(iTimer);
})
},[]);
- 针对AJAX数据请求,设置开关,阻止其setState
类组件关闭AJAX异步数据设置操作
componentDidMount(){
this._isMounted = true;
$.ajax('xxxxx',{})
.then(res => {
if(this._isMounted){
this.setState({
aa:true
})
}
})
.catch(err => {})
}
componentWillUnMount(){
this._isMounted = false;
}
或 网上的方法:
componentDidMount(){
$.ajax('xxxx',{})
.then(res => {
this.setState({
data: datas,
});
})
.catch(err => {})
}
componentWillUnmount(){
this.setState = (state,callback)=>{
return;
};
}
- 使用react.useEffect定义的变量xxxxxxx,不能定义在react.useEffect外面
Assignments to the 'xxxxxxx' variable from inside React Hook React.useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside React.useEffect
解决方式:
修改书写方式,如:
export default function Funcmp2(props) {
let [num,addNum] = React.useState(0)
//不能写在外面,要写在React.useEffect里面,去定义变量
//let iTimer = null; //错误的
React.useEffect(()=>{
let iTimer = setInterval(function(){
// addNum(num+1);
addNum((num)=>{
console.log(num+1);
return num+1
})
},1000);
return (()=>{
//第一个函数的返回值为 componentWillUnmount
clearInterval(iTimer);
})
},[]);
}
- 使用React.useState定义的state,在React.useEffect内进行数据修改时候,不能写成对象形式,要写成函数形式
React Hook React.useEffect has a missing dependency: 'num'. Either include it or remove the dependency array. You can also do a functional update 'addNum(n => ...)' if you only need 'num' in the 'addNum' call react-hooks/exhaustive-deps
解决方法:
export default function Funcmp2(props) {
let [num,addNum] = React.useState(0)
React.useEffect(()=>{
//错误
//addNum(num+1);
//正确
addNum((num)=>{
console.log(num+1);
return num+1
})
},[]);
}
- 给一个非ReactDOM.render的组件,进行组件移除操作
Warning: unmountComponentAtNode(): The node you're attempting to unmount was rendered by React and is not a top-level container. Instead, have the parent component update its state and rerender in order to remove this component.
解决方法:
父组件
export default class Head extends Component {
componentDidMount(){
//渲染一个自组件
ReactDOM.render(<Funcmp2 removeNodeId='timerBox' />,document.getElementById('timerBox'));
}
render() {
let {addTxt} = this.state;
return (
<div className="searchArea">
{ /* 定时器容器 */}
<div id="timerBox"></div>
<p>上面是函数式组件</p>
</div>
)
}
}
子组件
import React from 'react'
import ReactDOM from 'react-dom'
export default function Funcmp2(props) {
let [num,addNum] = React.useState(0)
React.useEffect(()=>{
let iTimer = setInterval(function(){
addNum((num)=>{
console.log(num+1);
return num+1
})
},1000);
return (()=>{
clearInterval(iTimer);
})
},[]);
function delFn(){
if(props.removeNodeId){
ReactDOM.unmountComponentAtNode(document.getElementById(props.removeNodeId));
}
}
return (
<div>
<h1>这是一个函数自增长的函数类型组件 {num}</h1>
<p>通过React.useEffect来开启一个定时器 </p>
<button onClick={delFn}>删除这个组件</button>
</div>
)
}
未完待续....