基础
-
1 state
(1)作用:用于组件保存、控制以及修改自己的状态
(2)setState:
作用:
> 核心作用:页面赋值 页面渲染
> 执行特点:每次调用都会触发render函数
> 在不同的场景下可能是同步,也可能时异步
setState异步情况
> 在合成函数中异步 如onClick=“方法” 合成事件
> 在类组件的生命周期中异步 如 componentDidMount 中
setState同步情况
> 在原生事件中 同步(原生事件:一个普通函数中)
> 在定时器中同步 -
2 props
作用:组件通信
传参方式
(1)逐个传参:
// 传递
<Person name="jerry" age={19} sex="男"/>
// 接收
class Person extends React.Component{
render(){
const {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
(2)批量传参:
// 传递
const person = {name:'老王',age:18,sex:'女'}
<Person {...person}/>
// 接收
class Person extends React.Component{
render(){
const {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
(3) this.props.children:表示所有组件的子节点
<MyNavLink to="/about">About</MyNavLink>
// 在MyNavLink组件中可以通过 this.props.children 获得 MyNavLink 包裹的内容值
class MyNavLink extends Component {
render() {
return (
<NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>
)
}
}
- 3 ref
作用:用于获取DOM
(1).创建容器 this.容器名 = React.createRef() // {current:null}
(2).把真实 DOM 存到容器中 在虚拟DOM 中注册 ref属性 如
(3).对容器的真实 DOM 进行操作 获取真实 DOM // this.容器名.current 就拿到了真实的 DOM - 4 JSX数据渲染
(1).相关知识点
JSX中使用 { } 来展示数据,这个{ } 也可以设置简单的JS表达式
布尔类型的数据,React无法直接在 { }进行渲染,需要经过处理
数组,React默认将数组拼接为字符串,需要调用map()方法进行处理
对象Object 不能直接展示,可以转化为JSON 字符串,也可以直接调用。
(2).渲染过程
使用JSX声明React元素结构,这个结构就是React生成真实的DOM模板
这个JSX模板被babel编译成React.createElement()
由React.createElement(component, props, …children)将这些React元素渲染成虚拟DOM
有render() 函数,讲这些虚拟DOM渲染为真实DOM,将虚拟DOM节点插入body中
(3).示例:
function Demo() {
let username = "小明";
let isMan = true;
let age = 30;
let arr = [10, 20, 30, 40];
let user = {
uname: "小红",
uage: 22,
};
return (
<div className="container">
<p>姓名:{username}</p>
<p>性别:{isMan},默认布尔类型数据,react无法直接展示</p>
<p>性别:{isMan ? "男" : "女"}</p>
<p>年龄:{age}</p>
<p>数组:{arr},默认React将数组拼接为字符串了</p>
<p>数组遍历:{arr.map((num) => num + "-")}</p>
<div>
数组遍历:
{arr.map((num) => (
<div>
<span>{num}</span>
</div>
))}
</div>
<div>
数组遍历:
{arr.map((num) => {
return (
<div className="d">
<a href="#">{num}</a>
</div>
);
})}
</div>
<div>
对象:{JSON.stringify(user)},注意对象不能直接展示,可以转成JSON字符串
</div>
<div>
对象姓名:{user.uname},对象年龄:{user.uage}
</div>
</div>
);
}
(4).截图
- 5 常用的判空方法
(1) 判断数组为空的方法
> arr.length=0 数组为空
> JSON.stringify([])=‘[]’ 结果:true 不推荐 简单介绍一下
(2) 判断对象为空的方法
> Object.keys({}).length === 0 结果:true
> JSON.stringify({})===‘{}’ 结果:true 不推荐 简单介绍一下
(3) 数组过滤数据的常用方法
> 可过滤掉数组中的 0 '' NaN undefined null false
[1,0,'',3,NaN,undefined,null,true,false].filter(Boolean) => [1, 3, true]
> 可过滤掉数组中的 null NaN undefined
[1,2,null,undefined,9,0,NaN,,'',true,false].filter(x=>!!x==true||x==0) => [1, 2, 9, 0, '', true, false]
分析:
!!:是逻辑"非非",即是在逻辑“非”的基础上再"非"一次
!!null => false
!!undefined => false
!!NaN => false
!!'' => false
''==0 => true
!!0 => false
- 6 项目中的健壮性处理
(1) 目的:防止因为后端返回的数据为空,前端界面拿不到值,而导致页面崩溃的问题
(2) 解决方法:
* 借助插件
> 思路:判断返回值是否为空,若为空则赋初始值
> 实现:可以借助 lodash 插件
import _ from 'lodash'; // 组要手动安装 lodash 库
// 在数据请求回来后进行处理,如果请求回来的数据为空则赋初始值
const inspPlant = _.get(res.data, 'inspectionPlan', '');
const cutNoticeList = _.get(res.data, 'cutNotice', []);
const number = _.get(res.data, 'number', 0);
* js原生方法
> ?. : 可选链运算符(?.)类似于链式运算符(.),不同之处在于在引用为null或undefined时不会报错,而是短路返回undefined
> ?? : 只有当??左侧为null或undefined时,才会返回右侧的值,否则返回左侧值 (可以给空数据赋初始值)
let a = null;
let b = 1;
// 可以用于 判断数据是否有值 如果没有值 赋初始值
let c = a ?? b; // 1
通信
-
props 传值
1.单项数据流
2.示例import { useState } from 'react'; const Son = (props) => { const {name} = props; return <div>{name}</div>; }; const Father = () => { const [name, setName] = useState('Jack'); return ( <> {/* 父组件通过属性的形式向子组件传值 */} <Son name={name} /> </> ); }; export default Father;
-
子组件向父组件传值
1.子组件调用父组件方法:将父组件的方法 以属性的形式通过 props 传递给子组件,子组件就可以调用了
(1).示例import { useState } from 'react'; const Son = ({ setCount }) => { return <button onClick={() => setCount(count => count + 1)}>点击+1</button>; }; const Father = () => { const [count, setCount] = useState(0); return ( <> <div>计数值:{count}</div> {/* 向子组件传递函数,子组件通过调用函数修改父组件数据 */} <Son setCount={setCount} /> </> ); }; export default Father;
-
React.createContext()发布者和消费者传值
1.第一中方法:函数组件和类组件都实用
(1).创建一个{Provider,Consumer} = React.createContext(‘默认值’)对象
(2).Provider和Consumer 都是一个组件
(3).Provider 组件中必须写一个 value 的值
(3).Consumer必须写在Provider中 用于 订阅 Provider里的value的值
(4).示例const { Provider, Consumer } = React.createContext(); // 子类组件 function Son() { return ( <div> <Consumer> {/* 接收传递的数据 */} {({ setCount, count }) => { return <button onClick={() => setCount(count + 1)}>+</button>; }} </Consumer> </div> ); } // 父类组件 function Father() { const [count, setCount] = React.useState(0); const data = { setCount, count }; return ( <Provider value={data}> <div> {count} <Son /> </div> </Provider> ); }
2.第二种写法 (省略了Consumer组件)类组件的写法
(1).创建一个objName = React.createContext(‘默认值’)对象 注意:objName 是自己起的名字 里面有Provider,Consumer组件
(2).使用 :objName.Provider
(3).子组件中 直接定义 一个静态变量 static contextType = objName // 源码中就会 把objName.Provider里的value的值 注:this.context中 等价于 this.context = objName.Provider 里的 value的值
(4).这种方法只能写在类组件中 因为函数组件没有this
(5).示例const CounterContext = React.createContext(); // 子类组件 class Son extends React.Component { static contextType = CounterContext; render() { //接收传递过来的参数 const { setState, count } = this.context; return <button onClick={() => setState({ count: count + 1 })}>+</button>; } } // 父类组件 class Father extends React.Component { state = { count: 0 }; render() { // 需要传递的参数 const data = { setState: this.setState.bind(this), count: this.state.count, }; return ( <CounterContext.Provider value={data}> <div> {this.state.count} <Son /> </div> </CounterContext.Provider> ); } }
3.第三种写法 借助useContext可以直接拿到值
(1).示例const CounterContext = React.createContext(); // 子类组件 function Son() { const { count, setCount } = React.useContext(CounterContext); return <button onClick={() => setCount(count + 1)}>+</button>; } // 父类组件 function Father() { const [count, setCount] = React.useState(0); const data = { setCount, count }; return ( <CounterContext.Provider value={data}> <div> {count} <Son /> </div> </CounterContext.Provider> ); }
-
redux:
1.redux是什么
解释:(1).redux是一个专门用于做状态管理的JS库(不是react插件库)。
(2).作用: 集中式管理react应用中多个组件共享的状态。
2.工作流程
3.核心方法
(1)getState()
(2)dispatch(action)
(3)subscribe(listener)
4.具体编码:
(1)store.getState()
(2)store.dispatch({type:‘INCREMENT’, number})
(3)store.subscribe(render)
5.react-redux:一个react插件库,专门用来简化react应用中使用redux
(1) 定义 UI 组件: UI 组件只做渲染 功能通过 props传递过来function Counter(props){ const {value,increment,decrement} = props; return ( <div> <h1>{value}</h1> <button onClick={increment}>+</button> <button onClick={decrement}>-</button> </div> ) }
(2) 用 prototypes 规定参数类型 ->此步骤可有可无 需要下载包prop-types
import PropTypes from "prop-types"; Counter.prototypes = { value:PropTypes.number.isRequired, increment:PropTypes.func.isRequired }
(3) 定义映射关系 定义映射派发 state的 关系 映射 state 就规定这么写的 state是 react-redux 库里封装的
let mapStateToProps = (state)=>{ // 返回一个对象 return {value:state.count} } // 简写方式 let mapStateToProps = (state)=>({value:state.count})
// 映射派发方法的 action let mapDispatchToProps = (dispatch)=>{ // 返回一个对象 return { // UI组件中可以通过props解构出increment decrement方法 increment:()=>dispatch(incrementAction()), decrement:()=>dispatch(decrementAction()) } } // 简写方式 :直接等于一个对象 自动调用 dispatch let mapDispatchToProps = { increment:()=>incrementAction(), decrement:()=>decrementAction() }
(4)用connect把映射关系传递进去,返回个函数 并且把 UI 组件传递进去 进行加强类似于高阶组件
const Container = connect(mapStateToProps,mapDispatchToProps)(StudentForm)
(5) 在App跟组件中 并用 Provider 进行连接 store
import store from './redux/store'; import { Provider } from 'react-redux'; class App extends React.Component{ render(){ return ( <Provider store={store}> <Container/> </Provider> ) } }
生命周期
类组件
一 static defaultProps
1 static defaultProps 写在类里 写法 : static defaultProps={ name:"lisi" }
2 给 props 设置默认属性
3 给 this.props = {name:"lisi"} 赋值
二 constructor - 创建实例
1 super(props) 属性直接挂载到组件实例上 :优先使用 this.props
2 可以获取父组件传值 props
3 状态初始化 this.state = {n:0}
4 this 统一绑定 使this指向实例 this.go = this.go.bind(this)
三 conponentWillMount - 组件将要挂载
1 只会执行一次
2 此时可以获取组件实例 相关的 props 和 state
3 可以在这里通过setState 修改 state 状态数据 :但是不能触发 render 的二次渲染(因为生命周期按顺序执行,在render钩子函数没执行之前就已经修改完数据了 所以不用再次执行render)
4 不赞成 在这里初始化 this.state = {n:0} 会报出 Warning
四 componentWillReceiveProps(nextProps) 父组件传值
1 父组件 重新渲染 子组件也会重新渲染 子组件的重新渲染从componentWillReceiveProps 钩子函数开始
2 子组件的数据状态 依赖于props; 可以在此进行重新计算
3 nextProps;拿到最新的数据
componentWillReceiveProps(nextProps){
console.log('====componentWillReceiveProps');
console.log('m'+':'+nextProps.m);// nextProps;拿到最新的数据
// 子组件的数据状态 依赖于props; 可以在此进行重新计算
let n = this.state.n
this.setState({
n: n + nextProps.m
})
}
五 shouldComponentUpdate(nextProps,nextState) 数据是否应该更改
1 核心作用 : 性能优化
2 需要有个布尔类型的返回值
return true -> 允许修改数据
return false -> 不允许修改数据
3 性能优化逻辑判断 : 本质判断状态(state)和属性(props) 看是否需要重新渲染 来优化渲染频率 来提高性能
this.state : 当前状态
nextState :修改后状态
this.props : 当前 props 信息
nextProps :新传入的props信息
六 componentWillUpdate - 组件将要更新
七 render - 渲染组件
1 render : 第一次或重新进行视图渲染:解析 jsx (构建视图虚拟DOM) => 渲染成真实DOM(diff 算法) => 用户展示(更新DOM)
2 render() 方法 class 组件中唯一必须实现的方法
3 render() 也是纯函数 :state 数据不修改 则返回相同结果
4 创建虚拟 DOM , diff 算法更新 DOM 树
5 必须返回 return
八 componentDidUpdate - 组件已经更新
1 组件更新完毕 立即调用; 注意 :组件第一次加载时候不调用
2 除了第一次不调用 之后每次 更新页面都会调用一次
九 componentDidMount - 组件第一次渲染完毕
1 componentDidMount : 第一次渲染完毕 (组件挂载完(插入真实DOM) 后立即调用)
2 只会执行一次
3 在这里去后台接口中的数据合适(ajax,axios请求的后台数据)
十 componentWillUnmount - 组件卸载
1 卸载 :资源释放 和 清理工作
2 什么时候调用(场景) : 比如清除定时器/取消订阅等
旧版本生命周期执行顺序
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
(1).constructor()
(2).componentWillMount()
(3).render()
(4).componentDidMount() ==> 常用
2. 更新阶段: 由组件内部this.setSate()或父组件render触发
(1).shouldComponentUpdate()
(2).componentWillUpdate()
(3).render() ==> 必须使用的一个
(4).componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
(1).componentWillUnmount() ==> 常用
旧版本生命周期图
最新版本生命周期
新增钩子 getDerivedStateFromProps (翻译:得到一个派生的状态从 props 中)
getDerivedStateFromProp:
1.触发时机:挂载更新是都会触发
2.书写形式必须以静态方法的形式进行书写
3.接收两个参数:state props
4.返回值是null 或者是 状态对象(状态对象:state相同的对象类似于 {count:0} )
5.特点:state 值在任何时候都取决于 props
6.使用场景:状态state的值完全取决于props的时候可以使用此钩子
例子:
class Form extends Component {
state = {
email: this.props.defaultEmail,
prevUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// 当前用户随时更改,
// 重置绑定到该用户的任何状态部分.
if (props.userID !== state.prevUserID) {
return {
prevUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}
7.可以用props里的值和初始化里state里的值做一个对比,判断以谁为主
新增钩子 getSnapshotBeforeUpdate (翻译:在更新之前获取快照)
getSnapshotBeforeUpdate:
1.触发时机:在最近一次渲染输出(提交到 DOM 节点)之前调用
2.作用:它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)
例如:获取之前浏览器视口的宽度 或 获取之前列表的宽度高度 传递到componentDidUpdate中处理
3.特点:此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()
注:componentDidUpdate(preProps,preState,snapshotValue)可以接收三个参数
4.代码案例:
class Demo extends React.Component{
state = {messagesArr:[]}
componentDidMount(){
setInterval(() => {
//获取原状态
const {messagesArr} = this.state
//模拟消息
const messages = '消息'+ (messagesArr.length+1)
//更新状态
this.setState({messagesArr:[messages,...messagesArr]})
}, 1000);
}
getSnapshotBeforeUpdate(){
// 获更新最后一条消息前的高度
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps,preState,height){
// this.refs.list.scrollHeight:更新后滚动区域的总高度
// scrollTop:向上滚动的高度
// height:更新最后一条消息前的高度
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render(){
return(
<div className="list" ref="list">
{
this.state.messagesArr.map((n,index)=>{
return <div key={index} className="messages">{n}</div>
})
}
</div>
)
}
}
新版本生命周期执行顺序
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
(1).constructor()
(2).getDerivedStateFromProps
(3).render()
(4).componentDidMount() ==> 常用
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
(1).getDerivedStateFromProps
(2).shouldComponentUpdate()
(3).render()
(4).getSnapshotBeforeUpdate
(5).componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
(1).componentWillUnmount() ==> 常用
新版本生命周期图
新旧版本生命周期差异
1.新版本即将废弃的钩子 (目前不推荐使用)
(1).componentWillMount
(2).componentWillReceiveProps
(3).componentWillUpdate
2.新版本新增两个钩子
(1).getDerivedStateFromProps
(2).getSnapshotBeforeUpdate
函数组件
1 Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
2 React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
3 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
4 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
路由《5&&6》
一 react-router-dom5
App组件需要 BrowserRouter 包裹
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
)
引入实现路由所需的组件,以及页面组件
<ul className="nav nav-tabs">
<li>
<NavLink to="/home/news">News</NavLink>
</li>
<li>
<NavLink to="/home/message">Message</NavLink>
</li>
</ul>
{/* 注册路由 */}
// Switch:命中一个就跳出,提高效率的东西
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>
</Switch>
路由传参
- params参数
(1) 传递params参数
(2) 接收params参数<div> <ul> { messageArr.map((msgObj)=>{ return ( <li key={msgObj.id}> {/* 向路由组件传递params参数 */} <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> </li> ) }) } </ul> <hr/> {/* 声明接收params参数 */} <Route path="/home/message/detail/:id/:title" component={Detail}/> </div>
// 路由组件中的props上会有match参数中的params可以直接解构到传递的数据 render() { // 接收params参数 // 接收到的数据结构:{id:'',title:""} const {id,title} = this.props.match.params const findResult = DetailData.find((detailObj)=>{ return detailObj.id === id }) return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> ) }
- search参数
(1) 传递search参数
(2) 接收search参数<ul> { messageArr.map((msgObj)=>{ return ( <li key={msgObj.id}> {/* 向路由组件传递search参数 */} <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> </li> ) }) } </ul> <hr/> {/* search参数无需声明接收,正常注册路由即可 */} <Route path="/home/message/detail" component={Detail}/>
// 接收search参数 // 接收到的数据结构:'?id=1&title=22'; const {search} = this.props.location; // parse:一个插件用于格式化接收到的search参数 => {id:'',title:""} const {id,title} = qs.parse(search.slice(1)) const findResult = DetailData.find((detailObj)=>{ return detailObj.id === id }) return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> )
- state参数
(1) 传递state参数
(2) 接收state参数<ul> { messageArr.map((msgObj)=>{ return ( <li key={msgObj.id}> {/* 向路由组件传递state参数 */} <Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link> </li> ) }) } </ul> <hr/> {/* state参数无需声明接收,正常注册路由即可 */} <Route path="/home/message/detail" component={Detail}/>
render() { // 接收state参数 // 接收到的数据类型:{id:'',title:""} const {id,title} = this.props.location.state || {} const findResult = DetailData.find((detailObj)=>{ return detailObj.id === id }) || {} return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> ) }
编程式路由导航
- 编程式路由传递参数
state = { messageArr:[ {id:'01',title:'消息1'}, {id:'02',title:'消息2'}, {id:'03',title:'消息3'}, ] } // history:history是路由组件中自带的属性 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.state return ( <div> <ul> { messageArr.map((msgObj)=>{ return ( <li key={msgObj.id}> <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> ) }
- 编程式路由接收参数:接收形式不变
render() { console.log(this.props); // 接收params参数 // const {id,title} = this.props.match.params // 接收search参数 // const {search} = this.props.location // const {id,title} = qs.parse(search.slice(1)) // 接收state参数 const {id,title} = this.props.location.state || {} const findResult = DetailData.find((detailObj)=>{ return detailObj.id === id }) || {} return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> ) }
二 react-router-dom6
App组件需要 BrowserRouter 包裹
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
)
引入实现路由所需的组件,以及页面组件
import React, { Suspense } from "react";
import { NavLink, useRoutes } from "react-router-dom";
import Loding from "./components/Loding";
export default function App() {
// 此部分可以进行抽离单独写
const element = useRoutes([{
path: "/about",
element: <About/> ,
},
{
path: "/home",
element: <Home/> ,
children: [{
path: "news",
element: <News/>
},
{
path: "message",
element: <Message/> ,
children: [{
path: 'detile',
element: <Detile/>
}]
}
]
},
{
path: "/",
element: <Navigate to = "/about"/> ,
},
]);
return (
<div>
<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">
<NavLink className="list-group-item " to="/about">
About
</NavLink>
<NavLink className="list-group-item" end to="/home">
Home
</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
<Suspense fallback={<Loding />}>
{/* 写法一: Routes替换了react-router-dom5中的Switch */}
{/* <Routes>
<Route path="/about" element={<About />} />
<Route path="/home" element={<Home />} />
<Route path="/" element={<Navigate to="/about" />} />
</Routes> */}
{/* 写法二: element需要配合useRoutes hooks来写 */}
{element}
</Suspense>
</div>
</div>
</div>
</div>
</div>
);
}
路由传参
- params参数
(1) 传递params参数
(2) 接收params参数const navigate = useNavigate(); // 编程式路由跳转 const show = (o) => { // 注意 只有 state 跳转可以写在第二个参数的配置项里,parms和search传递参数必须写在路径里 navigate(to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}); }; return ( <div> <ul> {message.map((o) => { return ( <li key={o.id}> {/* 导航式路由跳转 */} <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}> {o.title} </Link> <button onClick={() => show(o)}>编程式路由跳转</button> </li> ); })} </ul> <hr /> <Outlet /> </div> );
// useParams : import { useParams } from "react-router-dom";获得 const {id,title} = useParams() return ( <ul> <li>消息编号:{id}</li> <li>消息标题:{title}</li> </ul> );
- search参数
(1) 传递search参数
(2) 接收search参数const navigate = useNavigate(); // 编程式路由跳转 const show = (o) => { navigate(to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}); }; return ( <div> <ul> {message.map((o) => { return ( <li key={o.id}> {/* 导航式路由跳转 */} <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}> {o.title} </Link> <button onClick={() => show(o)}>编程式路由跳转</button> </li> ); })} </ul> <hr /> <Outlet /> </div> );
// useSearchParams : import { useSearchParams } from "react-router-dom";获得 const [search, setSearch] = useSearchParams(); const id = search.get("id"); const title = search.get("title"); return ( <ul> <li>消息编号:{id}</li> <li>消息标题:{title}</li> </ul> );
- state参数
(1) 传递state参数
(2) 接收state参数const navigate = useNavigate(); const show = (o) => { // 注意 只有 state 跳转可以写在第二个参数的配置项里,parms和search传递参数必须写在路径里 navigate("detile", { replace: false, state: { id: o.id, title: o.title, content: o.content, }, }); }; return ( <div> <ul> {message.map((o) => { return ( <li key={o.id}> <Link to="detile" state={{ id: o.id, title: o.title, content: o.content }} > {o.title} </Link> <button onClick={() => show(o)}>编程式路由跳转</button> </li> ); })} </ul> <hr /> <Outlet /> </div> );
// useLocation : import { useLocation } from "react-router-dom";获得 const { state: { id, title, content }, } = useLocation(); return ( <ul> <li>消息编号:{id}</li> <li>消息标题:{title}</li> </ul> );
react-router-dom5与react-router-dom6的差异
- v6中Switch名称变为Routes
// v5 import { HashRouter,Route,Switch } from 'react-router-dom' <HashRouter> <Switch> <Route path="/home" component={ Home }></Route> <Route path="/about" component={ About }></Route> </Switch> </HashRouter> // v6 import { HashRouter,Route,Routes } from 'react-router-dom' //Routes替换了Switch <HashRouter> <Routes> <Route path="/home" element={ <Home/> }></Route> <Route path="/about" element={ <About/> }></Route> </Routes> </HashRouter>
- 不再支持子组件和 component , 改为 element
// v5 <Route path='/login' component={Login}></Route> // v6 <Route path='/login' element={ <Login /> }>
- v6 移除 Redirect组件,改为使用 Navigate
// v5 <Redirect to="/home" /> // v6 <Route path="/" element ={<Navigate to="/home" />} />
- v6 嵌套路由改为相对匹配 : 相对v5 path 路径不用写太长了
// v5 <Switch> <Route path="/home/news" component={News}/> <Route path="/home/message" component={Message}/> </Switch> // v6 <Route path='/home' element={<Home />}> <Route path='news' element={<News />}></Route> <Route path='message' element={<Message />}></Route> </Route>
react-router-dom6 的 常用的路由和 hooks
组件名 | 作用 | 说明 |
---|---|---|
<Routes> | 一组路由 | 代替原有的<Switch>,所有子路由都用基础的Router children来表示 |
<Route> | 基础路由 | Route是可以嵌套的 |
<Link> | 导航组件 | 在实际页面中跳转使用 |
<Outlet> | 自适应渲染组件 | 根据实际路由url自动选择组件 |
hooks | 作用 | 说明 |
---|---|---|
useParams | 返回当前参数 | 根据路径读取参数 |
useNavigate | 返回当前路由 | 代替原有V5中的useHistory |
useOutlet | 返回根据路径生成的element | |
useLocation | 返回当前的location对象 | |
useRoutes | 同Routes组件一样,只不过是在js中使用 | |
useSearchParams | 用来匹配URL中?后边的搜索参数 |
拓展
- Fragment:空标签
1.作用:可以不用必须有一个真实的DOM根标签了
2.示例:<Fragment><Fragment> <></>
- 插槽:render props
解:通过props传递属性,属性为函数,函数调用后返回组件,也可进行传递参数
1.如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
2.children props
3.render props<A> <B>xxxx</B> </A> {this.props.children}
4.示例代码<A render={(data) => <C data={data}></C>}></A> A组件: {this.props.render(内部state数据)} C组件: 读取A组件传入的数据显示 {this.props.data}
export default class Parent extends Component { render() { return ( <div className="parent"> <h3>我是Parent组件</h3> <A render={(name)=><C name={name}/>}/> </div> ) } } class A extends Component { state = {name:'tom'} render() { const {name} = this.state return ( <div className="a"> <h3>我是A组件</h3> {this.props.render(name)} </div> ) } } class B extends Component { render() { console.log('B--render'); return ( <div className="b"> <h3>我是B组件,{this.props.name}</h3> </div> ) } }
- useReducer : 类似于 redux
import { useReducer } from "react";
// import { useReducer } from "./principle";
export default function AddCom() {
const reducer = (state, action) => {
switch (action.type) {
case "add":
return state + 1;
case "subtraction":
return state - 1;
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, 0);
return (
<div>
<p>{state}</p>
{ /* 派发更新 */ }
<button onClick={() => dispatch({ type: "add" })}>+</button>
<button onClick={() => dispatch({ type: "subtraction" })}>-</button>
</div>
);
}
// 分析:
import { useState } from "react";
export const useReducer = (reducer, initState) => {
const [state, updateState] = useState(initState);
const dispatch = (action) => {
updateState(reducer(state, action));
};
return [state, dispatch];
};