文章目录
- 1. 基本语法
- 1.1 初体验Hello React
- 1.2 JSX语法的基本使用
- 1.2.1 语句与表达式说明
- 1.3. React面向组件编程
- 1.3.1 函数组件与类组件
- 1.4 组件实例的三大特性
- 1.4.1 state数据存储状态
- 1.4.2 props的使用
- 1.4.2.1基本使用
- 1.4.2.2 做限制类型,默认值使用
- 1.4.2.3 简写方式
- 1.4.2.4 函数组件中使用props
- 1.4.3 refs的使用
- 1.4.3.1 直接使用ref绑定字符串
- 1.4.3.2 回调函数的形式
- 1.4.3.3 createRef的方式
- 1.5. React事件处理与委托
- 1.6.受控组件与非受控组件
- 1.6.1 非受控组件
- 1.6.2受控组件
- 1.7. 高阶函数与函数柯里化
- 1.7.1 柯里化回调
- 1.7.2 高阶回调
- 1.8. 组件的生命周期
- 1.8.1 旧生命周期
- 1.8.2 新生命周期
- 1.8.3 _getSnapshotBeforeUpdate的使用场景
- 2. react脚手架基本配置
- 2.1 创建项目与项目基本结构
- 2.2 全局样式与模块样式
- 2.3 配置代理,fetch与axios发送请求
- 2.3.1 代理配置
- 2.3.2 fetch发送网络请求
- 3 组件间的通信
- 3.1 父子通信,props ,事件
- 3.2 refs 与 事件冒泡
- 3.3 详解通信与类型限制
- 3.4 消息订阅-发布机制
- 3.4.1 React18 eventBus使用
- 4. 路由
- 4.1 路由的基本使用
- 4.2 Navlink 的选中样式
- 4.2.1标签体的内容children
- 4.3 Swith 单一匹配
- 4.4 模糊路由与精准匹配
- 4.5 路由重定向`Redirect`
- 4.6 嵌套路由
- 4.7 路由组件中的传参
- 4.7.1 params传参
- 4.7.2 Search传参
- 4.7.3 state传参
- 4.7.4 总结
- 4.8 编程式导航
- 4.9 BrowserRouter与HashRouter的区别
- 4.react-router6+版本
- 4.1 .Component
- 4.1.1 ` 与 `
- 4.1.2 `<Link> 与<NavLink>`
- 4.1.3. `<Navigate>`
- 4.1.4 `<Outlet>`
- 4.2 路由Hooks
- 4.2.1 useRoutes()
- 4.2.2 useNavigate()编程式导航
- 4.2.3 useParams()
- 4.2.4 ueSearchParams()
- 4.2.5 useLocation() state传参
- 4.2.6. useMatch()
- 4.2.7 useInRouterContext()
- 4.2.8 useNavigationType()
- 4.3 补充 类组件增强 withRouter
- 5. redux
- 6. React Hooks 及其扩展
- 6.1 setState的两种用法
- 6.1.1 对象更新
- 6.1.2 函数回调式更新
- 6.2 Hooks 之 useState
- 6.3 Hooks之useEffect
- 6.4 Hooks之useRef
- 6.5 Fragment代替根标签
- 6.6 Context的使用
- 6.6.1 提供的API
- 6.6.2 注意点
- 6.3 useContext
- 6.7 PureComponent 拒接子组件render重新渲染
- 6.8 render props 插槽
- 6.8.1 `this.props.children `渲染
- 6.8.2 `this.props` 渲染
- 6.8.3 `render props` 渲染
- 6.9 错误异常边界
1. 基本语法
1.1 初体验Hello React
<div id="app"></div><!-- 引入react核心库 --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom,用于支持react操作DOM --><script type="text/javascript" src="../js/react-dom.development.js"></script><!-- 引入babel,用于将jsx转为js --><script type="text/javascript" src="../js/babel.min.js"></script><script type="text/babel">const demo = <span>Hello Word</span>ReactDOM.render(demo, document.querySelector('#app'))</script>
1.2 JSX语法的基本使用
- jsx语法规则:
- 渲染定义的元素,表达式需要
{ }
const name = 'Hello Word'
<span style={{color:'red',fontSize:'55px'}}>{name}</span>
- 样式的类名,要用
className
。
<div className="className" id={ID}><span style={{color:'red',fontSize:'55px'}}>{name}</span>
</div>
- 内联样式,要用
style={{key:value}}
的形式去写。
<span style={{color:'red',fontSize:'55px'}}>{name}</span>
- 只有一个根标签
- 标签必须闭合
undefined
/null
/Boolean
类型
<h2>{String(aaa)}</h2><h2>{bbb + ""}</h2><h2>{ccc.toString()}</h2>
1.2.1 语句与表达式说明
-
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式:
(1). a
(2). a+b
(3). demo(1)
(4). arr.map()
(5). function test () {} -
语句(代码):
(1).if(){}
(2).for(){}
(3).switch(){case:xxxx} -
混入
map
表达式
const data = ['Vue', 'React', 'Animation']// 只能渲染数组const VDOM = (<div><h1>HEllo REACT</h1><h2>React遍历对象与数组</h2><ul>{data.map((v, index) => {return <li key={index}>{v}</li>})}</ul></div>)
ReactDOM.render(VDOM, document.querySelector('#test'))
1.3. React面向组件编程
1.3.1 函数组件与类组件
- 函数组件
注意点
- 函数式组件定义时首字母必须
大写
render
渲染时必须使用标签
- 函数式组件定义时首字母必须
function MyComponent(){console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>}ReactDOM.render(<MyComponent/>,document.getElementById('test'))
- 类组件
注意点
- 类组件必须继承React.Component
- 必须写render函数
- 必须有返回值
class MyComponent extends React.Component {render(){//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。//this指向?—— MyComponent的实例对象 <=>MyComponent组件实例对象。console.log('render中的this:',this);return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>}}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
1.4 组件实例的三大特性
1.4.1 state数据存储状态
注意点
React默认开启严格模式
- 普通函数的形式直接在事件中调用
this
的指向undefined
可以在构造函数中利用bind
,apply
,call
改变this的指向 - setState 用于更新state中的数据,里面包含一个对象要改变的值 (
注意点,setState是异步的
,可以传递对象或者函数,且每次调用render函数
都会重新渲染)
// state使用 class Wether extends React.Component {// 1. 继承React组件实例上添加的属性// 2. 构造器的this指向构造函数的实例对象// 3. render() 函数中的this也指向构造函数的实例对象constructor(props) {// super继承父组件属性super(props)this.state = { isHost: false, wind: '炎热' }// 改变this的指向this.demo = this.demo.bind(this)}render() {const { isHost } = this.state// this.function 是直接调用this指向windowreturn (<div onClick={this.demo} >{isHost ? '雨天' : '晴天'}</div>)}demo() {// this.state.isHost = !this.state.isHost // 取反 状态不能直接更改(React响应捕捉不到)let isHost = this.state.isHost// 修改状态需要用setStatethis.setState({ isHost: !isHost })}}ReactDOM.render(<Wether />, document.querySelector('#test'))
- 简写的方式
class Wether extends React.Component {// constructor(props) {// super(props)// // 改变this指向// this.speck = this.speck.bind(this)// }// 简写: 直接原型上追加属性state = {name: '张三',isName: true}render() {let { isName, name } = this.statereturn (<div onClick={this.speck} style={{ fontSize: '24px', color: 'red' }}> {isName ? '显示名字' : `不显示${name}`}</div>)}// 直接追加到原型上这里的this就是指原型speck = () => {let isName = this.state.isName// setState里面必须包含一个对象this.setState({ isName: !isName })}}ReactDOM.render(<Wether />, document.querySelector('#test'))
1.4.2 props的使用
1.4.2.1基本使用
- 基本使用
props
直接在实例上的 key=value 会追加到React实例props上 - 对象解构的方式使用
class Person extends React.Component{render(){// console.log(this);const {name,age,sex} = this.propsreturn (<ul><li>姓名:{name}</li><li>性别:{sex}</li><li>年龄:{age+1}</li></ul>)}}//渲染组件到页面ReactDOM.render(<Person name="jerry" age={19} sex="男"/>,document.getElementById('test1'))ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))const p = {name:'老刘',age:18,sex:'女'}// 对象解构的方式使用 ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
1.4.2.2 做限制类型,默认值使用
实例.propTypes={ }
对象里面包含要限制的数据类型实例.defaultProps={ }
对象里面包含的是默认的属性值
class DataLimit extends React.Component {speck=()=>{console.log(this.props)}render() {const { name, age, sex } = this.props// 注意点为props为只读属性不能修改return (<div><h2>{name}</h2><h2>{age+1}</h2><h2>{sex}</h2><h2 onClick={this.speck}> 点击事件</h2></div>)}}// propType 限制类型 (是否必传等)// 1.PropTypes.string 限制为字符串// 2.PropTypes.string.isRequired 限制为必传// 3. 限制方法为funcDataLimit.propTypes = {name: PropTypes.string.isRequired,sex: PropTypes.string,speak: PropTypes.func}// prop传值 默认值DataLimit.defaultProps = {sex: "女"}const data = { name: '张珊珊', age: 18, sex: "男" }ReactDOM.render(<DataLimit {...data} />, document.querySelector('#test1'))
1.4.2.3 简写方式
注意点
static
关键字给类添加属性
//类中可以直接写赋值语句,如下代码的含义是:给Car的实例对象添加一个属性,名为a,值为1class Car {constructor(name,price){this.name = namethis.price = price// this.wheel = 4}a = 1wheel = 4static demo = 100}const c1 = new Car('奔驰c63',199)console.log(c1);console.log(Car.demo); // 100
- . 给定类
DataLimit
添加propTypes
与defaultProps
两个属性做限制
// static 给类添加属性class DataLimit extends React.Component {speck = () => {console.log(this)}render() {const { name, age, sex } = this.props// 注意点为props为只读属性不能修改return (<div><h2>{name}</h2><h2>{age + 1}</h2><h2>{sex}</h2><h2 onClick={this.speck}> 点击事件</h2></div>)}static propTypes = {name: PropTypes.string.isRequired,sex: PropTypes.string,speak: PropTypes.func}// prop传值 默认值static defaultProps = {sex: "女"}}// (DataLimit.对象) 与直接在类里面用(static 对象) 一样console.log(DataLimit.propTypes);const data = { name: '张珊珊', age: 18, sex: "男" }ReactDOM.render(<DataLimit {...data} />, document.querySelector('#test1'))
1.4.2.4 函数组件中使用props
注意点
- 默认值传参 以及函数接受的值
function Person(prop) {const { name, age, sex } = propreturn (<div><li><a href="">{name}</a></li><li><a href="">{age}</a></li><li><a href="">{sex}</a></li></div>)}// 函数式组件限制Person.propTypes = {name: PropTypes.string.isRequired,sex: PropTypes.string,speak: PropTypes.func}// prop传值 默认值Person.defaultProps = {sex: "女"}const data = { name: '张珊珊', age: 18, sex: "男" }ReactDOM.render(<Person {...data} />, document.querySelector('#test1'))
1.4.3 refs的使用
refs
操作dom
1.4.3.1 直接使用ref绑定字符串
- 用ref绑定的dom会被收集到 refs这个对象中
class PersonRefs extends React.Component {clickRef = () => {console.log(this); // {Input:dom节点 }console.log(this.refs.Input);}render() {// 字符串形式的refreturn (<div><input type="text" ref="Input"/><button ref="button" onClick={this.clickRef}>点击Refs </button><input ref="input02" type="text" /></div>)}}ReactDOM.render(<PersonRefs />, document.querySelector('#test'))
1.4.3.2 回调函数的形式
class RefsFunc extends React.Component {addInput = () => {alert(this.input.value)// const { input1 } = this// alert(input1.value)}state = {isShow: true}isShowEvent = () => {const { isShow } = this.stateconsole.log(isShow);this.setState({ isShow: !isShow })}// ref 中写成这个只会回调一次CurrentEvent = (vnode) => {this.input02 = vnodeconsole.log('xxxxxx');}render() {//🌎 默认回调一次//🌎更新时,调用两次// Vnode => this.input1 = Vnode 回调函数 ref 回调形式return (<div><input type="text" ref={CurrentNode => { this.input = CurrentNode; console.log('更新调用两次'); }} defaultValue="默认值" /><input type="text" ref={this.CurrentEvent} /><input type="text" ref={Vnode => this.input = Vnode} defaultValue="默认值" /><button onClick={this.addInput}> 函数形式的Input使用 </button><p>{this.state.isShow ? "更新false" : "更新true"}</p><button onClick={this.isShowEvent}>切换内联函数调用</button></div>)}}ReactDOM.render(< RefsFunc />, document.querySelector('#test'))
1.4.3.3 createRef的方式
React.createRef
调用后可以返回一个容器,该容器可以存储被ref所标识的节点,返回一个要ref绑定的dom节点, 且key唯一
class RefsFunc extends React.Component {// 实例上添加一个myInputmyInput = React.createRef()componentDidMount = () => {console.log(this);console.log(this.myInput.current.value);// this.currentRefs.current.focusTextInput();}render() {return (<div><input type="text" ref={this.myInput} /><button onClick={this.componentDidMount}>createRef生成容器标识refDOM节点</button></div>)}}ReactDOM.render(< RefsFunc />, document.querySelector('#test'))
参看官网:createRef官网链接
1.5. React事件处理与委托
- 操作的事件与要操作的组件数据在同一个dom节点时,利用事件委托的方式
class Demo extends React.Component{//展示左侧输入框的数据 refsshowData = ()=>{alert(this.refs.myRefs.current.value);}// 操作的事件与要操作的组件数据在同一个dom节点时,利用事件委托的方式//展示右侧输入框的数据(target 处理数据)showData2 = (event)=>{alert(event.target.value);}render(){return(<div><input ref={e=>this.myrefs=e} type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/> </div>)}}//渲染组件到页面ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
1.6.受控组件与非受控组件
1.6.1 非受控组件
- 获取要提交的值为现用现取
class Login extends React.Component{handleSubmit = (event)=>{event.preventDefault() //阻止表单提交const {username,password} = thisalert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)}render(){return(<form onSubmit={this.handleSubmit}>用户名:<input ref={c => this.username = c} type="text" name="username"/>密码:<input ref={c => this.password = c} type="password" name="password"/><button>登录</button></form>)}}//渲染组件ReactDOM.render(<Login/>,document.getElementById('test'))
1.6.2受控组件
//受控组件 , 事件触发Input中 传在数据的值class Login extends React.Component{//初始化状态state = {username:'', //用户名password:'' //密码}//保存用户名到状态中saveUsername = (event)=>{this.setState({username:event.target.value})}//保存密码到状态中savePassword = (event)=>{this.setState({password:event.target.value})}//表单提交的回调handleSubmit = (event)=>{event.preventDefault() //阻止表单提交const {username,password} = this.statealert(`你输入的用户名是:${username},你输入的密码是:${password}`)}render(){return(<form onSubmit={this.handleSubmit}>用户名:<input onChange={this.saveUsername} type="text" name="username"/>密码:<input onChange={this.savePassword} type="password" name="password"/><button>登录</button></form>)}}//渲染组件ReactDOM.render(<Login/>,document.getElementById('test'))
1.7. 高阶函数与函数柯里化
- 高阶函数
- 如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
- 若A函数,接收的参数还是一个函数,那么A就可以称之为高阶函数。
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
- 常见的高阶函数有:
Promise、setTimeout、arr.map()
等等
- 函数柯里化 参考链描] 让函数的职责不再单一
1.7.1 柯里化回调
注意点
onChange={this.InputName('username')('xxxx')}
共调用三次,react调用,默认返回的值为target
/创建组件class Login extends React.Component {// 初始化状态state = {username: "默认值",password: ""}// 实时更新状态, 数据维护在state中为受控组件(相当于vue里面的v-model)InputName = (dataType) => {// onChange默认的返回一个函数//回调的是一个函数 (既函数的柯里化)return (Type) => {console.log([Type]);return (e) => {this.setState({ [dataType]: e.target.value })}}}InputPassWord = (e) => {this.setState({ password: e.target.value })}handlySubmit = (e) => {e.preventDefault();alert(`userName: ${this.state.username} passwrod : ${this.state.password}`,)}render() {return (<div><form onSubmit={this.handlySubmit} >{ /* <div>value 绑定默认值</div>*/}<input type="text" value={this.state.username} onChange={this.InputName('username')('xxxx')} name="username" /><input type="text" onChange={this.InputPassWord} name="password" /><button>提交</button></form></div>)}}ReactDOM.render(<Login />, document.querySelector('#test'))
1.7.2 高阶回调
class Login extends React.Component {// 初始化状态state = {username: "默认值",password: ""}// 实时更新状态, 数据维护在state中为受控组件(相当于vue里面的v-model)InputName = (dataType, event) => {// onChange默认的返回一个函数// [datatype]使用变量作为属性名this.setState({ [dataType]: event.target.value })}InputPassWord = (e) => {this.setState({ password: e.target.value })}handlySubmit = (e) => {e.preventDefault();alert(`userName: ${this.state.username} passwrod : ${this.state.password}`,)}render() {return (<div><form onSubmit={this.handlySubmit} >{ /* <div>不用可里化的方式实现1. onChange 先调用一个event函数在event 函数中又调用了this.InputName这个函数 </div>*/}<input type="text" value={this.state.username} onChange={(event) => {this.InputName('username', event)}} name="username" /><input type="text" onChange={this.InputPassWord} name="password" /><button>提交</button></form></div>)}}ReactDOM.render(<Login />, document.querySelector('#test'))
1.8. 组件的生命周期
1.8.1 旧生命周期
- 初始化阶段: 由ReactDOM.render()触发—初次渲染
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount() =====> 常用一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
- 更新阶段: 由组件内部this.setSate()或父组件render触发
1. shouldComponentUpdate()2. componentWillUpdate()3. render() =====> 必须使用的一个4. componentDidUpdate()
- 卸载组件:
1. 由ReactDOM.unmountComponentAtNode()触发
2. componentWillUnmount() =====> 常用一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
4.生命周期执行顺序代码参考
class Count extends React.Component {constructor(props) {super(props)console.log('构造器,constructor');}state = {count: 1}handlyAdd = () => {let { count } = this.statecount++;this.setState({ count })}// 钩子// 1.组件将要挂载的钩子componentWillMount() {console.log('组件将要挂载的钩子 componentWillMount');}// 2. 挂载中 render() {console.log('挂载中 render');const { count } = this.statereturn (<div><p>当前的数字 {count}</p><button onClick={this.handlyAdd}>点我加一</button><button onClick={this.UnMountEvent}> 卸载组件</button><button onClick={this.mandatoryUpdate}> 强制更新,不改状态</button></div>)}//3 挂载完毕componentDidMount() {console.log('挂载完毕 componentDidMount');}// 更新的组件// 1. 组件是否可以更新 返回值ture 或falseshouldComponentUpdate() {console.log('组件是否可以更新 shouldComponentUpdate');// 🚗 注意点1. 这个方法不写默认可以更新 为true // 2. 方法写入了 ,没有return 默认为falsereturn true}// 2. 组件将要更新// 3. render(){}componentWillUpdate() {console.log('组件将要更新 componentWillUpdate');}// 4. 组件更新componentDidUpdate() {console.log('组件更新完毕 componentWillUpdate');}//999 卸载组件UnMountEvent = () => {console.log('卸载DOM节点 unmountComponentAtNode');ReactDOM.unmountComponentAtNode(document.querySelector('#test'))}// 强制更新 不走shouldComponentUpdate()函数mandatoryUpdate = () => {this.forceUpdate()}}// 父子组件生命周期class Myfalter extends React.Component {state = {name: "父组件信息"}fatherEmitSon = () => {const { name } = this.statethis.setState({ name: '修改父组件的信息' })}render() {const { name } = this.statereturn (<div><h2>我是父组件</h2><h3>-----------------------------------</h3><button onClick={this.fatherEmitSon}>修改父组件值传递给子组件 </button>< Myson name={name} /></div>)}}class Myson extends React.Component {render() {return (<div><h2>我是子组件</h2><p>我将要展示父组件的内容: <span style={{ color: 'red' }}>{this.props.name}</span></p></div>)}componentDidMount() {console.log('子组件挂载时调用 componentDidMount');}componentWillReceiveProps(props) {// 1. (第一次接受值默认没有调用)子组件更新触发的生命周期 可以传递值console.log('xxxxxxxxx', props);}shouldComponentUpdate() {console.log('组件是否可以更新 shouldComponentUpdate');// 🚗 注意点1. 这个方法不写默认可以更新 为true // 2. 方法写入了 ,没有return 默认为falsereturn true}// 2. 组件将要更新// 3. render(){}componentWillUpdate() {console.log('组件将要更新 componentWillUpdate');}// 4. 组件更新componentDidUpdate() {console.log('组件更新完毕 componentWillUpdate');}}// ReactDOM.render(<Count />, document.querySelector('#test'))ReactDOM.render(<Myfalter />, document.querySelector('#test'))
1.8.2 新生命周期
- 初始化阶段: 由ReactDOM.render()触发—初次渲染
1. constructor()
2. getDerivedStateFromProps()
3. render()
4. componentDidMount() =====> 常用一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
- 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1. getDerivedStateFromProps()
2. shouldComponentUpdate()
3. render()
4. getSnapshotBeforeUpdate()
5. componentDidUpdate()
- 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
-
注意点
UNSAFE_componentWillMount() , UNSAFE_componentWillUpdate()
等生命周期废弃 具体参考官网 -
生命周期代码参考
class Count extends React.Component {constructor(props) {super(props)console.log('构造器,constructor');}state = {count: 1}handlyAdd = () => {let { count } = this.statecount++;this.setState({ count })}// 2. 挂载中 render() {console.log('挂载中 render');const { count } = this.statereturn (<div><p>当前的数字 {count}</p><button onClick={this.handlyAdd}>点我加一</button><button onClick={this.UnMountEvent}> 卸载组件</button><button onClick={this.mandatoryUpdate}> 强制更新,不改状态</button></div>)}// 新增加的钩子(相当于将要挂载 或将要更新的钩子)// 用处: state值完全取决于props// 注意点 写入必须返回值static getDerivedStateFromProps(props, state) {console.log("新增加的钩子 getDerivedStateFormProps");console.log('state', state);// 返回一个对象return props}// 新增加的钩子 (在更新之前获取快照)// 注意点 必须返回一个快照 或nullgetSnapshotBeforeUpdate() {return '更新之前的值'}//3 挂载完毕componentDidMount() {console.log('挂载完毕 componentDidMount');}// 更新的组件// 1. 组件是否可以更新 返回值ture 或falseshouldComponentUpdate() {console.log('组件是否可以更新 shouldComponentUpdate');// 🚗 注意点1. 这个方法不写默认可以更新 为true // 2. 方法写入了 ,没有return 默认为falsereturn true}// 4. 组件更新完成(拿到之前的值,可以获取getSnapshotBeforeUpdate这个钩子return的值)componentDidUpdate(preProps, preState, preValue) {console.log('组件更新完毕 componentWillUpdate');console.log('组件更新完成', preProps, preState, preValue);}//999 卸载组件UnMountEvent = () => {console.log('卸载DOM节点 unmountComponentAtNode');ReactDOM.unmountComponentAtNode(document.querySelector('#test'))}// 强制更新 不走shouldComponentUpdate()函数mandatoryUpdate = () => {this.forceUpdate()}}// 父子组件生命周期class Myfalter extends React.Component {state = {name: "父组件信息"}fatherEmitSon = () => {const { name } = this.statethis.setState({ name: '修改父组件的信息' })}render() {const { name } = this.statereturn (<div><h2>我是父组件</h2><h3>-----------------------------------</h3><button onClick={this.fatherEmitSon}>修改父组件值传递给子组件 </button>< Myson name={name} /></div>)}}class Myson extends React.Component {render() {return (<div><h2>我是子组件</h2><p>我将要展示父组件的内容: <span style={{ color: 'red' }}>{this.props.name}</span></p></div>)}componentDidMount() {console.log('子组件挂载时调用 componentDidMount');}componentWillReceiveProps(props) {// 1. (第一次接受值默认没有调用)子组件更新触发的生命周期 可以传递值console.log('xxxxxxxxx', props);}shouldComponentUpdate() {console.log('组件是否可以更新 shouldComponentUpdate');// 🚗 注意点1. 这个方法不写默认可以更新 为true // 2. 方法写入了 ,没有return 默认为falsereturn true}// 2. 组件将要更新// 3. render(){}componentWillUpdate() {console.log('组件将要更新 componentWillUpdate');}// 4. 组件更新componentDidUpdate() {console.log('组件更新完毕 componentWillUpdate');}}ReactDOM.render(<Count />, document.querySelector('#test'))// ReactDOM.render(<Myfalter />, document.querySelector('#test'))
- 生命周期参考图 生命周期图解官网参考
1.8.3 _getSnapshotBeforeUpdate的使用场景
- 利用上次传递的高度实现滚动官网参考链接
// scrollTop 滚动的高度// scrollHeight 内容区域的高度class NewsList extends React.Component {state = {newList: []}componentDidMount() {setInterval(() => {const { newList } = this.statelet list = `新闻${newList.length + 1}`this.setState({ newList: [list, ...newList] })}, 1000)}getSnapshotBeforeUpdate() { return this.refs.list.scrollHeight}componentDidUpdate(preProps, preState, height) {// console.log(preProps, preState, height);// height 是 getSnapshotBeforeUpdate()传递的值this.refs}render() {return (<div className="list" ref="list">{this.state.newList.map((n, index) => {return <div key={index} className='news'> {n}</div>})}</div>)}}ReactDOM.render(<NewsList />, document.getElementById('test'))
2. react脚手架基本配置
2.1 创建项目与项目基本结构
- 全局安装:
npm i -g create-react-app
- 切换到想创项目的目录,使用命令:
create-react-app hello-react
- 项目架构
public中 index.html
文件说明
<meta charset="utf-8" /><!-- %PUBLIC_URL%代表public文件夹的路径 --><link rel="icon" href="%PUBLIC_URL%/favicon.ico" /><!-- 开启理想视口,用于做移动端网页的适配 --><meta name="viewport" content="width=device-width, initial-scale=1" /><!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) --><meta name="theme-color" content="red" /><metaname="description"content="Web site created using create-react-app"/><!-- 用于指定网页添加到手机主屏幕后的图标 --><link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /><!-- 应用加壳时的配置文件 --><link rel="manifest" href="%PUBLIC_URL%/manifest.json" /><title>React App</title></head><body><!-- 若llq不支持js则展示标签中的内容 --><noscript>You need to enable JavaScript to run this app.</noscript><!-- 根ID --><div id="root"></div>
2.2 全局样式与模块样式
- 新建css文件命名时以
文件名.module.css
命名 - 导入时采用 import xxx from xxxx
2.3 配置代理,fetch与axios发送请求
2.3.1 代理配置
-
第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
-
编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')module.exports = function(app) {app.use(proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)changeOrigin: true, //控制服务器接收到的请求头中host字段的值/*changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000changeOrigin默认值为false,但我们一般将changeOrigin值设为true*/pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)}),proxy('/api2', { target: 'http://localhost:5001',changeOrigin: true,pathRewrite: {'^/api2': ''}})) }
注意以上是react 17 以前版本 ,现在代理配置
const { createProxyMiddleware } = require("http-proxy-middleware");module.exports = function (app) {app.use("/students", // 匹配createProxyMiddleware({target: "http://localhost:5000",changeOrigin: true,// pathRewrite:{// '^/api':"" // 忽略// }}));
};
2.3.2 fetch发送网络请求
注意点
fetch 发送请求,请求成功数据在 res.json()
中
- 版本01
fetch(`https://api.github.com/search/users?q=${value}`).then((res) => {console.log("服务器连接成功");// 1.调用res.JSON() 获取连接成功的数据并返回return res.json();}).then((res) => {console.log("获取数据成功", res);}).catch((err) => {console.log("失败的回调");});
- 优化版本2
fetch(`https://api.github.com/search/users?q=${value}`).then((data) => console.log(data)).catch((error) => console.error(error));
- 优化版本3
const res = await fetch(`https://api.github.com/search/users?q=${value}`);const data = await res.json();console.log(data);
3 组件间的通信
3.1 父子通信,props ,事件
父组件在展示子组件时,会传递一些数据给子组件:采用如下方法
父组件通过
属性=值
的形式来传递给子组件数据,或采用解构的形式传参
子组件通过this.props
获取父组件传递过来的数据
export class App extends Component {constructor() {super()this.state = {books: [{name: "算法导论", price: 79},{name: "数据结构", price: 69},{name: "漫画算法", price: 59},]}}render() {const { books } = this.statereturn (<div>{/* 将数据传递给子组件 */}<Header books={books}/></div>)}
}
- 子组件接受父组件传递的数据
export class Header extends Component {render() {// 接受父组件传递过来的参数const { books } = this.propsreturn (<div><ul>{books.map(item => {return (<li key={item.name}>名称: {item.name} 价格: {item.price}</li>)})}</ul></div>)}
}
回调函数,子组件向父组件传递消息:
在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;
import React, { Component } from 'react'
import ConterButton from './c-cpn/ConterButton'export class App extends Component {
state = {conter: 100}changeConter() {let {conter}= this.stateconter++this.setState({ conter })}render() {const { conter } = this.statereturn (<div><h2>{conter}</h2>{/* 向子组件中传入一个事件 */}<ConterButton getConter={this.changeConter()}/></div>)}
}
export default App
- 子组件在按钮发生点击时, 对父组件的传递的函数进行回调,
import React, { Component } from 'react'export class ConterButton extends Component {btnClick() {// 当按钮发生点击事件时, 对父组件传递过来的函数进行回调this.props.getConter()}render() {return (<div><button onClick={this.btnClick}>+1</button></div>)}
}
export default ConterButton
3.2 refs 与 事件冒泡
- 父组件通过
React.createRef()
创建Ref,保存在实例属性myRef上。父组件中,渲染子组件时,定义一个Ref属性,值为刚创建的myRef。 - 父组件调用子组件的myFunc函数,传递一个参数,子组件接收到参数,打印出参数。
- 参数从父组件传递给子组件,完成了父组件向子组件通信。
import React, { Component, Fragment } from 'react';
class Son extends Component {myFunc(name) {console.log(name);}render() {return <div></div>;}
}// 父组件
export default class Father extends Component {this.myRef = React.createRef();componentDidMount() {// 调用子组件的函数,传递一个参数this.myRef.current.myFunc('Jack');}render() {return (<div><Son ref={this.myRef} /></div>);}
}
3.3 详解通信与类型限制
1. 类型限制 安装prop-types库
2. 导入 import PropTypes from ‘prop-types’
3. 使用 static propTypes={ 要限制的key:PropTypes.类型.是否必传}
类型限制官网链接参考
3.4 消息订阅-发布机制
下载: npm install pubsub-js --save
使用: import PubSub from ‘pubsub-js’
订阅 PubSub.subscribe(‘delete’, function(data){ });
发布 PubSub.publish(‘delete’, data)
- 组件A订阅信息
// A组件内的状态
state = {users:[],isFirst:true,isLoading:false,err:''
}
// 订阅了消息名为updateState的消息事件
componentDidMount(){// 方法返回两个值(第一个值是订阅与发布共有的属性,第二个是接受发布的信息)this.token = PubSub.subscribe('updateState',(_,data)=>{this.setState(data) // 将收到的状态data更新})}
// 页面销毁前删除消息订阅 以防消息泄露
componentWillUnmount(){PubSub.unsubscribe(this.token)
}
- 组件B发布信息
// 发布消息名为updateState的消息事件
PubSub.publish('updateState',{isFirst:false,isLoading:true})
axios.get(`https://api.github.com/search/users?q=${keyWord}`).then(res=>{PubSub.publish('updateState',{users:res.data.items,isLoading:false})
}).catch(err=>{PubSub.publish('updateState',{err:err.message,isLoading:false})
})
3.4.1 React18 eventBus使用
- 安装 npm i hy-event-store
- 创建实例
import { HYEventBus } from "hy-event-store"
const eventBus = new HYEventBus()
export default eventBus
- emit 传递事件
nextClick() {eventBus.emit("bannerNext", {nickname: "kobe", level: 99})}
- on 监听事件
componentDidMount() {eventBus.on("bannerNext", this.bannerNextClick, this)}
- 销毁,防止内存泄漏
componentWillUnmount() {eventBus.off("bannerNext", this.bannerNextClick)}
4. 路由
- 下载 react-router-dom: npm install --save react-router-dom
4.1 路由的基本使用
- 在原生中页面的跳转我们可以直接使用a标签跳转,但是react中,需要使用link
- 导航区的a标签改为Link标签
<Link to="/xxxxx">Demo</Link>
- 展示区写Route标签进行路径的匹配
// component 包裹要切换的组件
<Route path='/xxxx' component={Demo}/>
<App>
的最外侧包裹了一个<BrowserRouter>
或<HashRouter>
import React, { Component } from "react";
import { Link, BrowserRouter, Route } from "react-router-dom";
import Home from "./components/Home/index";
import About from "./components/About/index";/* BrowserRouter 只能有一个包裹因此可以调整到index中包裹 */
export default class App extends Component {render() {return (<BrowserRouter><div className="row"><div className="col-xs-offset-2 col-xs-8"><div className="page-header"><h2>React Router Demo</h2></div></div></div><div className="row"><div className="col-xs-2 col-xs-offset-2"><div className="list-group"><Link className="list-group-item" to="/about">About</Link><Link className="list-group-item" to="/home">Home</Link></div></div><div className="col-xs-6"><div className="panel"><div className="panel-body">{/* 注册路由 */}<Route path="/about" component={About} /><Route path="/home" component={Home} /></div></div></div></div></BrowserRouter>);}
}
4.2 Navlink 的选中样式
{/* Link 与NavLik的区别 activeClassName="选中的类名" */}<NavLink activeClassName="demo01" className="list-group-item" to="/about">About= </NavLink>{/* <NavLink className="list-group-item" to="/about"> About </NavLink> */}<NavLink className="list-group-item" to="/home"> Home</NavLink>
navlink的选中样式参考链接
4.2.1标签体的内容children
- 封装公共
MyNavlink
组件,this.props
可以接受组件的传值
import React, { Component } from "react";
import { NavLink } from "react-router-dom";
export default class MyNavLink extends Component {render() {// const { to } = this.props;console.log(this.props);// 1. this.props可以直接接受标签体的属性// 2. < NavLink children /> 可直接展开显示children对应的value值return (<div><NavLink className="list-group-item" {...this.props} /></div>);}
}
1.2 路由组件跳转,传值
<div className="row"><div className="col-xs-2 col-xs-offset-2"><div className="list-group">{/* 编写路由链接 */}{/* Link 与NavLik的区别 activeClassName="" */}{/* 1.children 对应的value about 可以不写在标签题里面 */}<MyNavLink to="/about" >About</MyNavLink><MyNavLink to="/home" children="Home" /></div></div><div className="col-xs-6"><div className="panel"><div className="panel-body">{/* 注册路由 */}<Route path="/about" component={About} /><Route path="/home" component={Home} /></div></div></div></div>
4.3 Swith 单一匹配
- 通常情况下,path和component是一一对应的关系。
- Switch可以提高路由匹配效率(单一匹配)。
默认情况下,单个
path
相同时,会匹配component
注册的所有页面
<Switch>
单一匹配,它的独特之处在于它专门呈现路由
4.4 模糊路由与精准匹配
1.默认使用的是模糊匹配:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2.开启严格匹配:<Route exact={true} path="/about" component={About}/>
4.5 路由重定向Redirect
参考链接
4.6 嵌套路由
- 嵌套路由可以采用模糊匹配或者精准匹配
to="/home"
代表一级路由,to="/home/news"
二级路由
<div><ul className="nav nav-tabs"><li><MyNavLink to="/home/news">News</MyNavLink></li><li><MyNavLink to="/home/message">Message</MyNavLink></li></ul>{/* 注册路由 */}<Switch><Route path="/home/news" component={News}/><Route path="/home/message" component={Message}/><Redirect to="/home/news"/></Switch>
</div>
4.7 路由组件中的传参
4.7.1 params传参
- 需要link 标签传参时,在路由上必须接受传递的参数
- 接受传递的参数用
this.props.match.params
4.7.2 Search传参
- search传参,在路由上不需要接受传递的参数
- 路由组件使用传递的参数用
this.props.location.search
- 获取到的search是urlencoded编码字符串,需要借助querystring解析
4.7.3 state传参
- state传递参数一般包含path与state,path路径,state传的属性与值
- 路由组件中接受传递的值使用
this.props.location.state
4.7.4 总结
1.params参数路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>`接收参数:this.props.match.params2.search参数路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>接收参数:this.props.location.search备注:获取到的search是urlencoded编码字符串,需要借助querystring解析3.state参数路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>接收参数:this.props.location.state备注:刷新也可以保留住参数
4.8 编程式导航
注意点
区分普通组件与路由组件,在普通组件中不能使用路由组件的API,比如:push,replace
等,要想使用需要借助 withRouter
对普通组件进行包裹
- 编程式导航借助this.prosp.history对象上的API对操作路由跳转、前进、后退
- 主要有
push()
,replace()
,go()
,goback()
等
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'
export default class Message extends Component {state = {messageArr:[{id:'01',title:'消息1'},{id:'02',title:'消息2'},{id:'03',title:'消息3'},]}replaceShow = (id,title)=>{//replace跳转+携带params参数//this.props.history.replace(`/home/message/detail/${id}/${title}`)//replace跳转+携带search参数// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)//replace跳转+携带state参数this.props.history.replace(`/home/message/detail`,{id,title})}pushShow = (id,title)=>{//push跳转+携带params参数// this.props.history.push(`/home/message/detail/${id}/${title}`)//push跳转+携带search参数// this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)//push跳转+携带state参数this.props.history.push(`/home/message/detail`,{id,title})}back = ()=>{this.props.history.goBack()}forward = ()=>{this.props.history.goForward()}go = ()=>{this.props.history.go(-2)}render() {const {messageArr} = this.statereturn (<div><ul>{messageArr.map((msgObj)=>{return (<li key={msgObj.id}>{/* 向路由组件传递params参数 */}{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}{/* 向路由组件传递search参数 */}{/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}{/* 向路由组件传递state参数 */}<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link> <button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button> <button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button></li>)})}</ul><hr/>{/* 声明接收params参数 */}{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}{/* search参数无需声明接收,正常注册路由即可 */}{/* <Route path="/home/message/detail" component={Detail}/> */}{/* state参数无需声明接收,正常注册路由即可 */}<Route path="/home/message/detail" component={Detail}/><button onClick={this.back}>回退</button> <button onClick={this.forward}>前进</button> <button onClick={this.go}>go</button></div>)}
}
4.9 BrowserRouter与HashRouter的区别
- 底层原理不一样:
- BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
- HashRouter使用的是URL的哈希值。
- path表现形式不一样
- BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
- HashRouter的路径包含#,例如:localhost:3000/#/demo/test
- 刷新后对路由state参数的影响
- BrowserRouter没有任何影响,因为state保存在history对象中。
- HashRouter刷新后会导致路由state参数的丢失!!!
- HashRouter可以用于解决一些路径错误相关的问题。
4.react-router6+版本
-
React Router 以三个不同的包发布到 npm 上,它们分别为:
- react-router: 路由的核心库,提供了很多的:组件、钩子。
- react-router-dom: 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如
<BrowserRouter>
等 。 - react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:
<NativeRouter>
等。
-
与React Router 5.x 版本相比,改变了什么?
-
内置组件的变化:移除
<Switch/>
,新增<Routes/>
等。 -
语法的变化:
component={About}
变为element={<About/>}
等。 -
新增多个hook:
useParams
、useNavigate
、useMatch
等。
-
4.1 .Component
- v6版本中移出了先前的
<Switch>
,引入了新的替代者:<Routes>
。 <Routes>
和<Route>
要配合使用,且必须要用<Routes>
包裹<Route>
。<Route>
相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。<Route caseSensitive>
属性用于指定:匹配时是否区分大小写(默认为 false)。- 当URL发生变化时,
<Routes>
都会查看其所有子<Route>
元素以找到最佳匹配并呈现组件 。 <Route>
也可以嵌套使用,且可配合useRoutes()
配置 “路由表” ,但需要通过<Outlet>
组件来渲染其子路由。
4.1.1 <Routes/> 与 <Route/>
<><Link to="/about">About</Link><Link to="/home">Home</Link><ul className="nav">{/* 注册路由 */}<Routes><Route path="/about" element={<About />}></Route><Route path="/home" element={<Home />}></Route></Routes></ul></>
4.1.2 <Link> 与<NavLink>
- 注意: 外侧需要用
<BrowserRouter>
或<HashRouter>
包裹。 - NavLink与
<Link>
组件类似,且可实现导航的“高亮”效果。 - 示例代码:
<Link to="/路径">Home</Link><NavLink className={computedClassName} to="路径">Home</NavLink>
NavLink
高亮效果的实现
// 注意: NavLink默认类名是active,下面是指定自定义的class//自定义样式<NavLinkto="login"className={({ isActive }) => {console.log('home', isActive)return isActive ? 'base one' : 'base'}}>login</NavLink>/*默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。*/<NavLink to="home" end >home</NavLink>
4.1.3. <Navigate>
- 作用:只要
<Navigate>
组件被渲染,就会修改路径,切换视图。 replace
属性用于控制跳转模式(push 或 replace,默认是push)。
<Routes><Route path="/about" element={<About />}></Route><Route path="/home" element={<Home />}></Route>{/* 默认为push模式,replace为true时跳转为replace */}<Route path="/" element={<Navigate to="/home" replace={true} />}></Route></Routes>
4.1.4 <Outlet>
- 当
<Route>
产生嵌套时,渲染其对应的后续子路由。既指定路由呈现位置
const RoutesList = [{path: "/about",element: <About />,},{path: "/home",element: <Home />,children: [{path: "message",element: <Message />,},{path: "news",element: <News />,},],},
];// 二级路由<div className="NavContetn">{/* 指定路由组件呈现的位置Outlet */}<Outlet /></div>
4.2 路由Hooks
- 动态路由的传参
- 比如/detail的path对应一个组件Detail;
- 如果我们将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route,并且进行显示;
// router{path: '/detail/:id/:title',element: <Details />},
//navigate 传参,注意函数式组件与类组件navigate(`/detail/${id}/${title}`, { replace: true })
4.2.1 useRoutes()
- 作用:根据路由表,动态创建
<Routes>
和<Route>
。
//路由表配置:src/routes/index.jsimport About from '../pages/About'import Home from '../pages/Home'import {Navigate} from 'react-router-dom'export default [{path:'/about',element:<About/>},{path:'/home',element:<Home/>},{path:'/',element:<Navigate to="/about"/>}]//App.jsximport React from 'react'import {NavLink,useRoutes} from 'react-router-dom'import routes from './routes'export default function App() {//根据路由表生成对应的路由规则const element = useRoutes(routes)return (<div>......{/* 注册路由 */}{element}......</div>)}
4.2.2 useNavigate()编程式导航
- 作用:返回一个函数用来实现编程式导航。
import React from 'react'import {useNavigate} from 'react-router-dom'export default function Demo() {const navigate = useNavigate()const handle = () => {//第一种使用方式:指定具体的路径navigate('/login', {replace: false,state: {a:1, b:2}}) //第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法navigate(-1)}return (<div><button onClick={handle}>按钮</button></div>)}
4.2.3 useParams()
- 作用:返回当前匹配路由的
params
参数,类似于5.x中的match.params
。
// 路由表children: [{path: "details/:name",element: <Details />,},],
//导航路由传参<NavLink to={`Details?name=${UserInfo.name}`}> Details</NavLink>
// useParams接受参数
import { useParams } from 'react-router-dom'
export default function Details() {const add=useParams()return (<div>{add.name} </div>);
}
4.2.4 ueSearchParams()
- 作用:用于读取和修改当前位置的 URL 中的查询字符串。
- 返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。
//导航路由传参<NavLink to={`Details?name=${UserInfo.name}`}> Details</NavLink>
//接受参数
import { useSearchParams } from "react-router-dom";
export default function Details() {// setSearch 用于更新search 参数const [search, setSearch] = useSearchParams();console.log(search,setSearch);// setSearch('name="user"');// 返回的一个函数const name = search.get("name");return (<div> <li>{name}</li></div>);
}
4.2.5 useLocation() state传参
- 作用:获取当前 location 信息,对标5.x中的路由组件的
location
属性。
//导航路由传参<NavLink to="details" state={{ name: UserInfo.name }}> Details</NavLink>
//接受参数
import React from "react";
import { useLocation } from "react-router-dom";
export default function Details() {const {state:{name}}= useLocation();return (<div><li>{name}</li></div>);
}
4.2.6. useMatch()
- 作用:返回当前匹配信息,对标5.x中的路由组件的
match
属性。
<Route path="/login/:page/:pageSize" element={<Login />}/><NavLink to="/login/1/10">登录</NavLink>export default function Login() {const match = useMatch('/login/:x/:y')console.log(match) //输出match对象//match对象内容如下:/*{params: {x: '1', y: '10'}pathname: "/LoGin/1/10" pathnameBase: "/LoGin/1/10"pattern: {path: '/login/:x/:y', caseSensitive: false, end: false}}*/return (<div><h1>Login</h1></div>)}
4.2.7 useInRouterContext()
- 作用:如果组件在
<Router>
的上下文中呈现,则useInRouterContext
钩子返回 true,否则返回 false。
4.2.8 useNavigationType()
- 作用:返回当前的导航类型(用户是如何来到当前页面的)。
- 返回值:
POP
、PUSH
、REPLACE
。 - 备注:
POP
是指在浏览器中直接打开了这个路由组件(刷新页面)。 - 作用:给定一个 URL值,解析其中的:path、search、hash值。
4.3 补充 类组件增强 withRouter
- 类组件不能使用hooks,因此封装高阶组件 转换为函数式组件对类组件做增强
// hoc/index.js
import { useLocation, useNavigate, useParams } from 'react-router-dom';
export function withRouter(WapperWithRouter) {return function (props) {const navigate = useNavigate()const params=useParams()const location = useLocation ()const router = { navigate, params, location }return < WapperWithRouter {...props} router={router} />}
}
import React, { Component } from 'react';
import { Link, Outlet } from 'react-router-dom';
import { withRouter } from '../../hoc';class About extends Component {navigateTo(path){console.log(path);console.log(this.props.router.navigate(path));}render() {return (<div><Link to="/about/recommend">音乐列表展示</Link><Link to="actor"> 音乐艺人展示</Link>{/* 注意路径的匹配 to 与path保持一致 */}<button onClick={()=>this.navigateTo('classqa')}> 类组件如何实现跳转</button><div><Outlet /></div></div>)}
}
export default withRouter(About)
至此 类组件的props就有了路由的一些属性可以用navigate
5. redux
参考
参考2
6. React Hooks 及其扩展
- 在函数式组件中并没有this,因此React提供的Hooks,这就让你在函数式组件中可以使用state或其他特性
- 常使用的Hooks有
React.useState()
,React.useEffect()
,React.useRef()
6.1 setState的两种用法
注意点
: setState更新是异步的
6.1.1 对象更新
setState(stateChange, [callback])------对象式的setState
1. stateChange为状态改变对象(该对象可以体现出状态的更改)
2. callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
this.setState({count},()=>{console.log(this.state.count);})// 或者this.setState({count:count+1})
6.1.2 函数回调式更新
setState(updater, [callback])------函数式的setState
1. updater为返回stateChange对象的函数。
2. updater可以接收到state和props。
3. callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
- 函数回调直接接受 state
this.setState((state)=>( { count: state.count+1 }))
6.2 Hooks 之 useState
- State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
语法: const [xxx, setXxx] = React.useState(initValue)
- useState()说明:
- 参数: 第一次初始化指定的值在内部作缓存
- 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
- setXxx()2种写法:
- setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
- setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
import React from "react";
// 1. 注意点 函数式组件命名 首字母大写
// 2. useState 初始调用后会把值缓存起来,不会因为函数的再次调用把count重新赋值为0
// 3.hooks 必须在最顶层使用 不能在if for 等使用
// 4.useState 如果没有传递参数,那么初始化值为undefined
// 5. 箭头函数写法 const DemoCount= React.memo(()=>{ })
export default function DemoCount(params) {// 第一个是返回的值, 第二个参数返回一个函数设置第一个返回的值let [count, setCount] = React.useState(0);function add() {// count++;// setCount(count); //第一种写法setCount(count=>count+1)}return (<div><h1>{count}</h1><button onClick={add}>点击+1</button><button onClick={()=>setCount(count-1)}>点击-1</button></div>);
}
6.3 Hooks之useEffect
- Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类似组件中的生命周期钩子)
- React中的副作用操作:
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实DOM
语法: useEffect(() => { // 在此可以执行任何带副作用操作return () => { // 在组件卸载前执行// 在此做一些收尾工作, 比如清除定时器/取消订阅等}}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
- 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()componentDidUpdate()componentWillUnmount()
// 卸载组件
import React from "react";
function App() {const [showChild, setShowChild] = React.useState(true);function unmound() {setShowChild(false);}return (<>{showChild && <DemoCount />}<button onClick={unmound}>卸载组件</button></>);
}
// useEffect 相当于componentDidMount,或者 componentDidUpdate (主要看第二个值传不传)
function DemoCount() {let [count, setCount] = React.useState(0);React.useEffect(() => {let timer = setInterval(() => {setCount((count) => count + 1);}, 1000);return () => {// 返回值为清除定时器clearInterval(timer);};}, []); // 传一个数组,表示检测谁,(默认不传,检测所有,传空数组谁也不检测)function add() {setCount((count) => count + 1);}return (<div className="id"><h1>{count}</h1><button onClick={add}>点击+1</button></div>);
}
export default App;
6.4 Hooks之useRef
- Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
语法: const refContainer = useRef()
function App() {const myrefs = React.useRef();function getRefsValue() {console.log(myrefs.current.value);}return (<div><input ref={myrefs} type='text' /><button onClick={getRefsValue}>ref</button></div>);
}
6.5 Fragment代替根标签
- render 函数中都都需一个根标签,这样会都渲染一个不需要的dom节点,利用
Fragment
代替就不会渲染
import React, { Fragment } from "react";
// Fragment 忽略标签,|| <></> 区别在于是否需要key
function App() {const myrefs = React.useRef();function getRefsValue() {console.log(myrefs.current.value);}return (<Fragment><input ref={myrefs} type="text" /><button onClick={getRefsValue}>ref</button></Fragment>);
}
6.6 Context的使用
- Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过
组件树的逐层传递 props
- Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。
请谨慎使用,因为这会使得组件的复用性变差
6.6.1 提供的API
React.createContext
使用此API可以创建一个Context对象,组件会从最近的
Provider
中读取对应的值。只有当组件所处的树种不存在Provider时,defaultValue参数才会生效
const MyContext = React.createContext(defaultValue);
Context.Provider
- Context对象会返回一个Provider组件
- Provider接受一个value属性,传递给消费组件 当Provider的value属性值更变时,内部的所有消费组件都会重新渲染
- context会根据引用标识进行重新渲染,所以当向value传递一个对象时,需要注意:
当Provider重新渲染时,可能会触发Consumer意外渲染。为了防止这种情况,将value状态提升到父节点的state中
<MyContext.Provider value={某个值}/>
Context.Consumer
- Context对象会返回一个Consumer组件
- 需要一个函数作为子元素,函数接收
context
值,返回一个React
节点- 传递给函数的value值等价于组件树上方离这个
context
最近的Provider
提供的value值。如果没有对应的Provider
,value
参数等同传递给createContext()的defaultValue
<MyContext.Consumer>{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
Class.contextType
- 此属性可以让你使用
this.context
来获取最近Context
上的值。你可以在任何生命周期中访问到它,包括render函数中
const MyContext = React.createContext()
class MyClass extends React.Component {render() {let value = this.context;/* 基于这个值进行渲染工作 */}
}
MyClass.contextType = MyContext
- 同时,你也可以使用实验性的
public class fields语法中的static类属性初始化contextType
,此外一般都是使用第二种用法
const MyContext = React.createContext()
class MyClass extends React.Component {static contextType = MyContextrender() {let value = this.context;/* 基于这个值进行渲染工作 */}
}
6.6.2 注意点
- 在使用时,类组件使用
Provider
,Consumer
可以用于类组件和函数式组件
// A->B->C , context 组件通信
import React, { Component } from "react";
// 需求C组件展示A组件的Name
// 创建上下文context
const MyContext = React.createContext();
export default class App extends Component {state = {name: "我是A组件,需要在C组件中展示",nameInfo: "A组件的详细信息",};render() {const { name, nameInfo } = this.state;return (<div><h2>A</h2><h5>----------------------</h5><MyContext.Provider value={{ name, nameInfo }}><B /></MyContext.Provider></div>);}
}class B extends Component {render() {return (<><h2> B</h2><h5>----------------------</h5><C /></>);}
}
// class C extends Component {
// // 声明接受context
// static contextType = MyContext;
// render() {
// console.log(this.context); //
// return (
// <div>
// <h2>C</h2>
// {/* <a href="1">{this.context}</a> */}
// <h5>----------------------</h5>
// </div>
// );
// }
// }
// 函数式组件使用context,Provider只使用于类组件, Consumer 可以用于类组件和函数式组件
function C() {return (<div><h2>C</h2><MyContext.Consumer> {value=> {return `${value.name}`}}</MyContext.Consumer><h5>----------------------</h5></div>);
}
6.3 useContext
- Context Hook允许我们通过Hook来直接获取某个Context的值
import React, { memo, useEffect, useState } from 'react';
// import { ThemeContext } from './context/themContext';
// import { MyUserInfoContext } from './context/userInfo';
import Hooks from './hooks';
// 自定义hooks 需要以use开头
function useSumApp(Name) {useEffect(() => {console.log(Name)return () => {console.log(Name)}}, [Name])
}const App01 = memo(() => {// const themeStyleContext = useContext(ThemeContext)// const userInfoContext = useContext(MyUserInfoContext)const [themeStyleContext, userInfoContext] = Hooks()console.log(themeStyleContext);useSumApp('App01')return (<div>App01</div>)
})
const App02 = memo(() => {// const themeStyleContext = useContext(ThemeContext)// const userInfoContext = useContext(MyUserInfoContext)useSumApp('App02')return (<div>App02</div>)
})const App = memo(() => {const [isShow, setIsShow] = useState(true)return (<div><button onClick={() => setIsShow(!isShow)}>updata Component</button>{isShow && <App01 />}{isShow && <App02 />}</div>)
})export default App
hooks的封装
import { ThemeContext } from './context/themContext';
import { MyUserInfoContext } from './context/userInfo';import { useContext } from 'react';export default function Hooks() {const themeStyleContext = useContext(ThemeContext)const userInfoContext = useContext(MyUserInfoContext)return [themeStyleContext, userInfoContext]
}
6.7 PureComponent 拒接子组件render重新渲染
注意点
, 只要调用setState 父子组件中的render函数都会调用
- 避免上述情况,可以采用的方案为:
- 重写shouldComponentUpdate()方法 比较新旧state或props数据, 如果有变化才返回true,
- 如果没有返回false
shouldComponentUpdate(nextProps, nextState) {// 这里可以判断是否更新子组件的render 当nextState与this.state的值相同时,返回false不同返回ture ,简单点说就是阀门是否打开if (nextState.name === this.state.name) {return false;} else {return true;}}
- 使用
PureComponent
PureComponent重写了shouldComponentUpdate()
,- 只有state或props数据有变化才返回true 注意:
只是进行state和props数据的浅比较,
- 如果只是数据对象内部数据变了, 返回false 不要直接修改state数据, 而是要产生新数据
import React, { PureComponent } from "react";
// PureComponent 判断 子组件是否使用父组件的内容,数据更新时是否调用子组件的render
export default class App extends PureComponent {}
6.8 render props 插槽
- Vue中: 使用slot技术, 也就是通过组件标签体传入结构
- React中:使用children props: 通过组件标签体传入结构,使用render props: 通过组件标签属性传入结构, 一般用render函数属性
6.8.1 this.props.children
渲染
1. A组件使用
<B><C>xxxx</C>
</B>
2. 在B要使用C组件中调用 {this.props.children}渲染
- 但是上面也存在一个问题: 如果B组件需要A组件内的数据, ==> 做不到
6.8.2 this.props
渲染
// 父组件传递const btn = <button>按钮2</button>;<NavBarTwoleftSlot={btn}centerSlot={<h2>呵呵呵</h2>}rightSlot={<i>斜体2</i>}/>
// 子组件
const { leftSlot, centerSlot, rightSlot } = this.propsreturn (<div className='nav-bar'><div className="left">{leftSlot}</div><div className="center">{centerSlot}</div><div className="right">{rightSlot}</div></div>
- 图解
6.8.3 render props
渲染
<B render={(data) => <C data={data}></C>}></B>
B组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
- 图解
代码参考
import React, { PureComponent } from "react";
export default class App extends PureComponent {render() {return (<div style={{ width: "1200px", height: "300px", background: "red" }}><h2>A</h2><h5>--------App------------</h5>{/* 相当于Vue里面的插槽, */}<B render={(name) => <C name={name} />} /></div>);}
}
class B extends PureComponent {state = { name: "我是B组件需要在C组件展示" };render() {console.log("@,render Children");return (<divstyle={{width: "300px",height: "200px",margin: "0 auto",background: "#fff",}}><h2> B 组件</h2><h5>----------------------</h5>{/* 调用C组件的render */}{/* {this.props.children} */}{/* 第二种方式:预留插槽 */}{this.props.render(this.state.name)}</div>);}
}
class C extends PureComponent {render() {return (<div style={{ background: "#0f03d6", color: "#fff", height: "80px" }}><h2> C 组件</h2><a href="ccc" style={{color:'#ddcc00'}}> {this.props.name}</a></div>);}
}
6.9 错误异常边界
- 错误边界:用来捕获
后代组件
错误,渲染出备用页面
- 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
- 使用
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {console.log(error);// 在render之前触发// 返回新的statereturn {hasError: true,};
}
componentDidCatch(error, info) {// 统计页面的错误。发送请求发送到后台去console.log(error, info);
}