尚硅谷_React(2) - 两个案例+路由
官方脚手架
脚手架:用于帮助程序员快速创建一个基于XXX库的模板项目。 步骤:
- 安装:
npm i create-react-app -g
- 创建项目目录:
create-react-app hello-react
- 进入目录:
cd hello-react
- 启动项目:
npm start
脚手架项目结构: 功能界面的组件化编码流程:
- 拆分组件;
- 实现静态组件:使用组件实现静态页面效果;
- 实现动态组件:动态显示初始化数据;交互;
TodoList案例
- 拆分组件、实现静态组件,注意:className、style的写法
- 动态初始化列表,如何确定将数据放在哪个组件的state中?
——某个组件使用:放在其自身的state中 ——某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
- 关于父子之间通信:
- 【父组件】给【子组件】传递数据:通过props传递
- 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
- 注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value
- 状态在哪里,操作状态的方法就在哪里
脚手架配置代理
发送网络请求,发生了跨域,如localhost:3000给localhost:5000发请求。 在React中可以通过代理进行解决。这个代理就是中间的服务器 两种开启代理的方式:react脚手架配置代理.md 法一:package.json:
// ... 添加
"proxy": "http://localhost:5000"
然后请求函数中的变化为:
axios.get("http://localhost:5000/student") --- 原本请求5000端口
// 修改为
axios.get("http://localhost:3000/student") --- 改为请求3000端口,对应上图
但不是所有请求都转发给了端口5000,如请求:"http://localhost:5000/index.html"
,因为本地端口3000下有public/index.html。(因此先会在本地找,找不到就去请求5000端口)
法二:若要请求多个不同的服务器,就不在package.json中配置了。
若想在react最新版脚手架配置,会比较麻烦点。
新建:src/setupProxy.js
,React会自动查找该文件并执行
// 需要使用CommonJS的语法
//react脚手架自带了, 前文的package.json中配置只是简略版
const proxy = require('http-proxy-middleware')
module.exports = function (app){
app.use(
proxy('api1',{ // 如果请求路径中包含了api1,就会进行转发
target: "http://localhost:5000", // 请求转发给谁
changeOrigin: true, // 控制服务器收到的请求头中Host字段的值
pathRewrite: {"^/api1": ""} // 再将路径中的api1进行改写为''
}),
proxy('api2',{
target: "http://localhost:5001",
changeOrigin: true,
pathRewrite: {"^/api2": ""}
}),
)
}
github搜索案例
设计状态时要考虑全面
例如带有网络请求的组件,要考虑请求失败怎么办。List组件展示的信息的几种可能性:
- 用户信息列表
- 初始页面
- 请求中的loading
- 错误兜底页面
那就需要一些状态:
export default class App extends Component {
state = {
users: [], // 用户信息
isFirst: true, // 是否第一次打开页面
isLoading: false, // 是否加载中
err: "", // 存储请求相关错误信息
};
updateAppState = (stateObj) => {
this.setState(stateObj);
};
render() {
const { users } = this.state;
return (
<div className="container">
<Search updateAppState={this.updateAppState} />
<List {...this.state} />
</div>
);
}
}
更新状态的时机:发送请求前;请求成功后;请求失败后; 另外还需要利用三元表达式来写对应状态时所展示的页面。 但这里的各个状态是写在App组件中的,即子传父,父再传子,略繁琐,可利用工具库实现任意组件间的通信。
ES6小知识点:解构赋值+重命名
let obj = {a:{b:1}}
const {a} = obj; //传统解构赋值
const {a:{b}} = obj; //连续解构赋值
const {a:{b:value}} = obj; //连续解构赋值+重命名
消息订阅与发布机制
即node中的event Emitter,在京购小程序首页中有类似应用
- 先订阅,再发布(理解:有一种隔空对话的感觉)
- 适用于任意组件间通信
- 要在组件的componentWillUnmount中取消订阅
兄弟(任意)组件间的通信
消息发布与订阅机制,需要利用第三方工具。
先要订阅消息,定好消息名(你想要啥消息),之后再发布消息,才能收的到。
工具库:[PubSubJS](https://github.com/mroderick/PubSubJS)
,github搜索然后照着安装即可;
// 基本使用
// 订阅消息
var token = PubSub.subcribe("My TOPIC",(msg, data)=>{ // 消息名、回调
console.log(msg, data);
})
// 发布消息
// 发布以后,订阅对应消息的地方就会执行对应的回调函数
PubSub.publish("My TOPIC", "hello world");
**重点:**谁接东西,谁就在componentDidMount里订阅消息,在componentWillUnmount取消订阅;谁想给别人传东西,谁就发布消息;
fetch发送请求(关注分离的设计思想)
发送ajax请求,除了xhr
,还有fetch
;xhr不符合“关注分离”的设计;
第一步:联系服务器是否成功;
第二步:联系成功后拿到数据;联系失败
联系服务器是否成功和服务器是否响应你的地址是两码事
try {
const response= await fetch(`/api1/search/users2?q=${keyWord}`)
const data = await response.json()
console.log(data);
} catch (error) {
console.log('联系服务器失败、请求页面不存在等',error);
}
React路由
每一个路径对应着一个组件,这就是一种映射关系。一个路由就是一个映射关系(key:value
)。其中key为路径,value可能是function或component。
其中后端路由的key:value的value对应为function,前端为component
那如何实现的前端路由呢,即前端路由的原理?浏览器内置的history
对象;
区分路由和路由器的概念。①点击导航链接引起路径变化,②路径变化被路由器监测到,展示相应组件。
基本使用
步骤:
- 安装:
npm install --save react-router
- 使用:
// index.js
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<BrowserRouter> -- 需要包一下,history模式,一个路由器
<App/>
</BrowserRouter>,
document.getElementById('root')
)
// App.jsx
import { Link, Route } from "react-router-dom";
// ...
{/* 原生html中,靠<a>跳转不同的页面 */}
{/* <a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a>
*/}
{/* 在React中靠路由链接实现切换组件--编写路由链接-- 1️⃣ */}
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
{/* 注册路由 */} -- ②
<Route path="/about" component={About}/> -- 这里的About是路由组件
<Route path="/home" component={Home}/>
一般组件和路由组件
一般组件和路由组件最大的区别:
- 写法不同:
一般组件:<Demo/>
路由组件:<Route path="/demo" component={Demo}/>
- 存放位置不同:一般组件和路由组件放在不同的文件夹;一般组件放在component文件夹下,路由组建放在pages/Home,pages/About这样的形式;
- 一般组件是写标签时传递什么就接收什么;路由组件会收到路由器传递的props:
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"
封装NavLink
需要高亮效果使用NavLink
标签代替Link
标签,然后添加activeClassName
。
<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
可以封装一下NavLink让其更好用,封装为MyNavLink:
标签体内容,是特殊的标签属性,这样title就可以写在标签体里了
<NavLink className="list-group-item" to="/home">Home</NavLink>
// 相当于
<NavLink className="list-group-item" to="/home" children="Home" />
export default class MyNavLink extends Component {
render() {
// console.log(this.props);
return (
<NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>
)
}
}
// 使用
<MyNavLink to="/about">About</MyNavLink>
Switch的使用
Switch组件让路由匹配后就不会再继续匹配,提高了匹配的效率。
<Switch> // 引入,包一下就行
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
解决样式丢失问题
./
改为/
./
改为%PUBLIC_URL%/
- 使用hashrouter
路由的模糊匹配与精确匹配
默认是模糊匹配,严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由;
<MyNavLink to="/home/a/b">Home</MyNavLink>
<Route path="/home" component={Home}/> // 会匹配上
<Route exact path="/home" component={Home}/> -- 不会匹配
Redirect的使用
称为重定向,感觉像是兜底的人。一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由。
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
嵌套路由(二级路由)
路由的匹配都是按照注册顺序走的,因此注册子路由时要写上父路由的path值。/home/news
的路径会匹配/home
和/home/news
。
向路由组件传递params参数
// /Home/Message/index.jsx
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
// /Home/Message/Detail/index.jsx
// 这样路由组件的props就能收到params参数
const {id, title} = this.props.match.params
向路由组件传递search参数
Tips:获取到的search是urlencoded编码字符串,需要借助querystring解析
// /Home/Message/index.jsx
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
// /Home/Message/Detail/index.jsx
// 接收search参数
const {search} = this.props.location // "?id=01&title=消息1"
const {id,title} = qs.parse(search.slice(1)) // react脚手架内置了qs库
// key=value&key=value 这种编码形式是urlencoded
// qs.stringify将对象转为urlencoded编码形式,qs.parse反之
向路由组件传递state参数
Tips:刷新后location.state不丢,历史记录清除后刷新会丢,加上{}
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
// 接收state参数
const {id,title} = this.props.location.state || {}
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
}) || {}
push与replace模式
默认为push模式,添加replace={true}
开启replace模式,进行栈顶替换。
<Link replace={true} className="list-group-item" to="/about">About</Link>
编程式路由导航
即不以标签的形式写路由跳转,而是在代码事件中进行路由跳转。
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})
}
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})
}
back = ()=>{
this.props.history.goBack() // 后退
}
forward = ()=>{
this.props.history.goForward() // 前进
}
go = ()=>{
this.props.history.go(-2) // 后退2
}
withRouter的使用
用来干嘛的?只有路由组件的props才有接收到history等函数,一般组件没有;为了让一般组件也能用上history等函数,就需要用到withRouter。
// Header/index.jsx
import {withRouter} from 'react-router-dom'
class Header extends Component{...}
// withRouter的返回值是一个新组件
export default withRouter(Header)
BrowserRouter与HashRouter区别
- 底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。 HashRouter使用的是URL的哈希值。
- path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test HashRouter的路径包含#,例如:localhost:3000/#/demo/test
- 刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在history对象中。 (2).HashRouter刷新后会导致路由state参数的丢失!!!
- 备注:HashRouter可以用于解决一些路径错误相关的问题。
antd组件库
流行的开源ReactUI组件库:
- material-ui(国外)
- ant-design(国内蚂蚁金服)