react学习笔记
前言准备:
vscode常用插件安装:
①
输入react,安装 ES7+ React/Redux/React-Native snippets
rcc:生成类代码片段(react class component)
rfc:生成函数代码片段(react function component)
②
输入Chinese ,安装Chinese (Simplified) (简体中文) Language Pack for Visu
中文界面
③
prettier ,安装Prettier - Code formatter 代码格式化 shift+alt+F
④
sneak mark检测英文语句中的中文符号
⑤
Auto Close Tag 自动补全标签和联名重复标签
⑥
open in browser → 可以通过快捷键 “Alt + B”(mac: “option + B”) 来在浏览器上打开
html 文件
⑦
Simple React Snippets
⑧
EasyLess
1、react入门
一、简介
- 中文官网: https://react.docschina.org/
- 用于动态构建用户界面的 JavaScript 库(只关注于视图),由Facebook开源
二、特点
- 声明式编码
- 组件化编码
- React Native 编写原生应用
- 高效(优秀的Diffing算法)
三、高效原因
- 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
- DOM Diffing算法, 最小化页面重绘。
四 、jsx语法规则:
1、定义虚拟DOM时,不能加引号
2、标签中混入js表达式时要用{}才能取到值
3、样式的类名指定要用className
4、内联样式要用style={{key:value}}去写
5、jsx的跟标签只能有一个
6、标签必须闭合,也就是有 />
7、标签首字母
(1)若小写字母开头则转为html中的同名元素,如果html中无对应同名标签则报错
(2)若大写字母开头,react就去渲染对应的组件,若没有定义组件则报错
五、数组遍历的例子
<script type="text/babel">
//摸你数据
const data = ["Angular", "React", "Vue"];
//1、创建虚拟DOM
const VDOM = (
<div>
<h1>前端js框架列表</h1>
<ul>
{data.map((item,index) => {
return <li key={index}>{item}</li>;
})}
</ul>
</div>
);
ReactDOM.render(VDOM, document.getElementById("test"));
</script>
2、React面向组件编程
1 、组件
1.1、渲染组件
执行了:
ReactDOM.render(<Demo/>, document.getElementById("test"));
之后发生了什么?
1、React解析组件标签,找到Demo组件
2、发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真是DOM,随后呈现在页面
1.2 函数式组件
function Demo(){
return <h2>我是函数式组件</h2>
}
ReactDOM.render(<Demo/>, document.getElementById("test"));
1.3 类式组件
//1、创建类式组件
class MyComponent extends React.Component {
render() {
return <div>我是用类定义的组件</div>;
}
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
/*
* 执行了ReactDOM.render(<Demo/>, document.getElementById("test"));之后发生了什么?
* 1、React解析组件标签,找到Demo组件
* 2、发现组件是使用类定义的,随后new 出来该类的实例,并通过该实例调用到原型上render上的方法
* 3、将render返回的虚拟dom转为真是dom,随后呈现在页面中
*/
2、组件的三大属性
2.1 state
- state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
- 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
- 组件中render方法中的this为组件实例对象 组件自定义的方法中this为undefined,如何解决?
a) 强制绑定this: 通过函数对象的bind()
b) 箭头函数 状态数据,不能直接修改或更新.要用setState
<script type="text/babel">
//创建类式组件
class Weather extends React.Component {
//初始化状态
state = { isHot: false, wind: "大风" };
//每次更新状态都重新渲染 调用 1+n次
render() {
const { isHot, wind } = this.state;
return (
<h1 onClick={this.changeWeather}>
今天天气真 {isHot ? "热" : "凉快"},今天:{wind}
</h1>
);
}
//箭头函数没有自己的this,它找外层函数的this作为自己的this
changeWeather = () => {
const isHot = this.state.isHot;
//状态不可直接更改,需使用其api setState。它是一种合并更新,不是替换
this.setState({
isHot: !isHot,
wind: isHot ? "有风,吹得冷" : "没有风,热死了",
});
};
}
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
2.2 props
2.21 展开运算符 …
展开运算符:…
①展开数组 例如:
let arr = [1,2,3,4] ====> console.log(...p) ===>1,2,3,4
②连接数组
let arr1 = [1,2,3] let arr2 = [5,6,7]
===>let arr3=[...arr1,arr2]
===>console.log(arr3)
===>[1,2,3,5,6,7]
③数组的归约运算(java8中的归约也是差不多这样的)
console.log(sum(1,2,3,4));
===> function sum(...numbers){
return numbers.reduce((preValue,currentValue)=>{
return preValue+currentValue
})}
输出数组中的数字的和。
2.22 基本使用(props传值)
每个组件对象都会有props(properties的简写)属性
组件标签的所有属性都保存在props中
作用
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改props数据
<script type="text/babel">
class Person extends React.Component {
render() {
const { name, age, sex, salary } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
<li>工资:{salary}</li>
</ul>
);
}
}
const p = { name: "李四", age: "18", sex: "女", salary: "74323" };
ReactDOM.render(<Person {...p} />, document.getElementById("test"));
</script>
2.23 对props进行限制
Person.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string,//限制sex,为字符串
age: PropTypes.number,//限制age必传,为数字
speak: PropTypes.func,//限制speak,为函数
};
Person.defaultProps = {
sex: "不男不女",
};
2.24 函数使用props
function Person(props){
const {name,age,sex,salary} = props;
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age + 1}</li>
<li>性别:{sex}</li>
<li>工资:{salary}</li>
</ul>
);
}
const p = { name: "李6", age: 18, sex: "女", salary: 74323 };
ReactDOM.render(<Person {...p} />, document.getElementById("test3"));
3.refs
ref即是给当前所在行打标签,组件内的标签可以定义ref属性来标识自己
①字符串形式的ref(不推荐)
<script type="text/babel">
//创建组件
class MyComponent extends React.Component {
showData1 = () => {
const {input1} = this.refs
alert(input1.value)
};
showData2 = () => {
const {input2} = this.refs
alert(input2.value)
};
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData1}>点我提示左侧数据</button>
<input ref = "input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
②回调函数形式(内联)
<script type="text/babel">
//创建组件
class MyComponent extends React.Component {
showData1 = () => {
const {input1} = this
alert(input1.value)
};
showData2 = () => {
const {input2} = this
alert(input2.value)
};
render() {
return (
<div>
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData1}>点我提示左侧数据</button>
<input ref={c=>this.input2=c} type="text" onBlur={this.showData2} placeholder="点击按钮提示数据"></input>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
③类的绑定
<script type="text/babel">
//创建组件
class MyComponent extends React.Component {
state = { isHot: true };
showData1 = () => {
const { input1 } = this;
alert(input1.value);
};
changeWeather = () => {
const { isHot } = this.state;
this.setState({ isHot: !isHot });
};
saveInput = (c)=>{
this.input1 = c
console.log('@@',c)
}
render() {
const { isHot } = this.state;
return (
<div>
<h2>今天天气很{isHot ? "炎热" : "凉爽"}</h2>
{/*<input ref={c => {this.input1 = c;console.log('111')}} type="text" placeholder="点击按钮提示数据"/> */}
<input
ref={this.saveInput} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData1}>点我提示数据</button>
<button onClick={this.changeWeather}>点我切换天气</button>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
④createRef(推荐)
<script type="text/babel">
//创建组件
class MyComponent extends React.Component {
/*
*①React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
*/
myRef = React.createRef()
showData1 = () => {
console.log(this.myRef.current.value)
//③从Ref容器中取值
const myRefValue = this.myRef.current.value
alert(myRefValue)
};
render() {
return (
<div>
{/**②把当前节点存在容器myRef中**/}
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData1}>点我提示左侧数据</button>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
3、受控与非受控组件
非受控组件:
现用现取成为非受控组件
受控组件:
随着输入把输入值维护到state中
非受控组件
<script type="text/babel">
class Login extends React.Component
handleSubmit = (envent) => {
//组织默认事件,即不会触发表单提交事件
envent.preventDefault();
const { username,password } = this;
alert(`你输入的用户名:${username.value} 你输入的密码:${password.value}`);
};
render() {
return (
<form
action="http://www.atguigu.com"
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"));
</script>
受控组件
<script type="text/babel">
class Login extends React.Component {
//初始化
state = { username: "", password: "" };
handleSubmit = (envent) => {
//组织默认事件,即不会触发表单提交事件
envent.preventDefault();
const { username, password } = this.state;
alert("用户名: " + username + " 密码:" + password);
};
usernameChange = (event) => {
this.setState({ username: event.target.value });
};
passwordChange = (event) => {
this.setState({ password: event.target.value });
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:{" "}
<input
onChange={this.usernameChange} type="text" name="username" />
密码:{" "}
<input
onChange={this.passwordChange}
type="password"
name="password" />
<button>登录</button>
</form>
);
}
}
ReactDOM.render(<Login />, document.getElementById("test"));
</script>
4、函数的柯里化
这里的saveFormData就是高阶函数:
高阶函数符合任一一个就是:
1.若A函数,接收到的一个参数是函数
2.若A函数,调用的返回值依然是一个函数
函数的柯里化: 通过函数调用继续返回函数的方式,实现多次接收参数,最后统一处理的函数编码形式
常见高阶函数:Promise/setTimeout/arr.map()
<script type="text/babel">
class Login extends React.Component {
handleSubmit = (envent) => {
//组织默认事件,即不会触发表单提交事件
envent.preventDefault();
const { username, password } = this;
alert(
`你输入的用户名:${username.value} 你输入的密码:${password.value}`
);
};
saveFormData = (dataType) => {
return (event) => {
//[dataType] 是去获取dataType的值
this.setState({ [dataType]: event.target.value });
};
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:{" "}
<input
onChange={this.saveFormData("username")}
type="text"
name="username"
/>
密码:{" "}
<input
onChange={this.saveFormData("password")}
type="password"
name="password"
/>
<button>登录</button>
</form>
);
}
}
ReactDOM.render(<Login />, document.getElementById("test"));
</script>
简单的例子
function sum(a){
return (b)=>{
return (c)=>{
return a+b+c;
}
}
}
const result = sum(1)(2)(3)
不用柯里化解决上面的例子:
<script type="text/babel">
class Login extends React.Component {
handleSubmit = (envent) => {
//组织默认事件,即不会触发表单提交事件
envent.preventDefault();
const { username, password } = this.state;
alert(
"你输入的用户名:"+username+ "你输入的密码:"+password
);
};
saveFormData = (dataType, value) => {
this.setState({ [dataType]: event.target.value });
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:{" "}
<input
onChange={(event) => this.saveFormData("username", event)}
type="text"
name="username"
/>
密码:{" "}
<input
onChange={(event) => this.saveFormData("password", event)}
type="password"
name="password"
/>
<button>登录</button>
</form>
);
}
}
ReactDOM.render(<Login />, document.getElementById("test"));
</script>
5、组件的生命周期
① unmountComponentAtNode 卸载组件
② componentDidMount 组件渲染完成去调
③ componentWillUnmount组件将要卸载时执行
<script type="text/babel">
//创建组件
class Life extends React.Component {
state = { opacity: 0.54 };
fuckMao = () => {
//①unmountComponentAtNode 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById("test"));
};
//②componentDidMount 组件渲染完成去调
componentDidMount() {
this.timer = setInterval(() => {
console.log("@@@@");
//const定义是不能改变的
let { opacity } = this.state;
opacity -= 0.1;
if (opacity <= 0) {
opacity = 1;
}
this.setState({ opacity });
}, 1000);
}
//③组件将要卸载时执行
componentWillUnmount() {
clearInterval(this.timer);
}
render() {
return (
<div>
<h2 style={{ opacity: this.state.opacity }}>
*雪梅c不到怎么办?
</h2>
<button onClick={this.fuckMao}>*毛*</button>
</div>
);
}
}
ReactDOM.render(<Life />, document.getElementById("test"));
</script>
1.初始化阶段:
由ReactDOM.render()触发—初次渲染
- constructor() //构造器
- componentWillMount() //组件将要挂载
- render() //渲染
- componentDidMount() //组件挂载完成调用。常用。做初始化的事,例如开启定时器、发送网络请求、订阅消息
2.更新阶段:
由组件内部this.setSate()或父组件重新render触发
1、shouldComponentUpdate() //组件是否要更新(根据返回的true false决定)
2、componentWillUpdate() //组件将要更新
3、render()
4、componentDidUpdate() //执行更新组件
3.卸载组件:
由ReactDOM.unmountComponentAtNode()触发
1、componentWillUnmount() //将要卸载组件。常用。例如关闭定时器、取消订阅消息
17.x与16.x之间的差别:
- componentWillMount ==UNSAFE_componentWillMount
- componentWillReceiveProps == UNSAFE_componentWillReceiveProps
- componentWillUpdate ==UNSAFE_componentWillUpdate
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用
重要的勾子 常用
- render:初始化渲染或更新渲染调用
- componentDidMount:开启监听, 发送ajax请求
- componentWillUnmount:做一些收尾工作, 如: 清理定时器
6、Diff算法
为什么遍历时,key最好不要用index?
A.虚拟DOM中的key的作用
1.简单说:key是虚拟dom对象标识,在更新显示时key起着极其重要的作用
2.详细说:当状态中的数据发送变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React会进行【新的虚拟DOM】和【旧的虚拟DOM】的diff比较,如下规则:
a.旧的虚拟DOM中找到与新的虚拟DOM 相同的key,
(1)若虚拟DOM 中内容没有变化,则直接使用之前的真实DOM
(2)若虚拟DOM中的内容变化了,则生成新的虚拟DOM,随后替换掉页面中的真实DOM
b.旧虚拟DOM中未找到与新虚拟DOM相同的key,根据数据创建新的真实的DOM,随后渲染到页面
B.用index作为key可能会引发问题:
1.若对数据进行:逆序添加、逆序删除等破坏顺序操作
会产生没必要的真实DOM 更新,==> 界面效果没问题,但效率低
2.如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题
3.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表展示,用index作为key没有问题。
C.开发中如何选择key?
1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的数据展示,用index也是可以的
3、reat脚手架
1、安装脚手架
第一步,全局安装:npm install -g create-react-app
第二步,切换到想创项目的目录,使用命令:(hello-react是项目名字,随意起)
create-react-app hello-react
2、安装生成随机数的库
安装命令:yarn add nanoid
引入: import {nanoid} from ‘nanoid’
nanoid() 就是全局唯一的字符串,直接用
1、组件间传值:
1.1 父组件 -> 子组件
父组件传值:
<List todos={todos} />
子组件接收:
const { todos } = this.props;
1.2 子组件 -> 父组件
父组件传递函数:
{/**子组件向父组件传值 */}
<Header addTodo={this.addTodo}/>
父组件中的函数:
//用于添加一个todo,接收参数是todo对象
addTodo=(todoObj)=>{
console.log(todoObj)
//1.获取原todo,接收参数是todo对象
const {todos} = this.state
//2.追加一个todo
const newTodos = [todoObj,...todos]
//3.更新状态
this.setState({todos:newTodos})
}
子组件:
export default class Header extends Component {
//键盘按键松开
handleKeyUp = (event) => {
//结构赋值
const { target, keyCode } = event;
//判断回车
if (keyCode != 13) {
return;
}
//添加的名字不能为空
if(target.value.trim() === ''){
alert('输入不能为空!')
return
}
//准备好一个todo对象
const todoObj = { id: nanoid(), itemName: target.value, done: false };
//向父组件传值
this.props.addTodo(todoObj);
//清空输入框
target.value=''
};
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
);
}
}
注意: 在onClick传值时需要用高阶函数或者回调函数
//删除一条数据
handleDelete=(id)=>{
//todo
}
<button onClick={()=>this.handleDelete(id)}
className="btn btn-danger"
style={{ display: mouse ? "block" : "none" }}
>
删除
</button>
defaultChecked 只在第一次起作用,后面不起作用。要用checked.类似的还有defaultValue和value
2、axios
安装 axios:
yarn add axios
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:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: { "^/api1": "" }, //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy("/api2", {
target: "http://localhost:5001",
changeOrigin: true,
pathRewrite: { "^/api2": "" },
})
);
};
github项目遇到项目: 在setupProxy.js中,原写法,但是项目一直报错:crbug/1173575, non-JS
module files deprecated
const proxy = require('http-proxy-middleware')
module.exports = function (app) {
app.use(
proxy('/api1', {//遇见/api1前缀的请求,就会触发该代理配置
target: 'http://localhost:5000',//请求转发给谁
changeOrigin: true,//控制服务器收到的响应中Host字段的值
pathRewrite: {
'^/api1': ''
}
})
}
解决办法:proxy.createProxyMiddleware 代替proxy
module.exports = function (app) {
app.use(
proxy.createProxyMiddleware("/api1", {
//api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: "http://localhost:5000", //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
pathRewrite: { "^/api1": "" }, //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
})
);
};
3、消息订阅与发布
安装:
npm install pubsub-js --save 或 yarn add pubsub-js
使用步骤:
① 安装npm install pubsub-js --save 引入 import PubSub from 'pubsub-js
② 发布 PubSub.publish(‘atguigu’,{isFirst:false,isLoading:true})
③订阅 PubSub.subscribe
componentDidMount(){
this.token = PubSub.subscribe('atguigu',(msg,stateObj)=>{
console.log("list组件收到订阅发布数据:"+stateObj);
this.setState(stateObj);
})
}
④关闭订阅(这里的this.token是上面订阅处的名字)
componentWillUnmount(){
PubSub.unsubscribe(this.token)
}
4、 fetch的使用
fetch 请求 优化版 await 的使用要在最近的函数加 async
search = async () => {
try {
const response = await fetch(
`https://api.github.com/search/users?q=${value}`
);
const data = await response.json();
console.log(data);
PubSub.publish("atguigu", { isLoading: false, users: data.items });
} catch (error) {
console.log("请求出错(自己处理出错)", error);
}
}
4、路由
常用路由组件
1. <BrowserRouter>
2. <HashRouter>
3. <Route>
4. <Redirect>
5. <Link>
6. <NavLink>
7. <Switch>
其它
- history对象
- match对象
3.withRouter函数
1、使用路由
1.1 下载 (@是指定版本)
react-router-dom:
npm install --save react-router-dom @5.2.0 或者 yarn add react-router-dom
1.2、原生html中靠<a>
标签跳转到不同页面,在react中靠路由链接实现切换组
点击按钮About 跳往路径/about
<Link className="list-group-item" to="/about">About</Link>
1.3.路径映射组件
/about 路径映射组件About
<Route path="/about" component={About} />
1.4.Route与Link都需要包在BrowserRouter里面所以在最外层(这里是index.js)包上BrowserRouter
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'));
注:react-router-dom 6版本写法不一样①Route必须要包在Routes组件里②element 代替compoent
<Routes>
<Route path="/about" element={About} />
<Route path="/home" element={Home} />
</Routes>
2、路由组件与一般组件的区别:
①写法不同:
一般组件:<Demo/>
路由组件:<Route path="/demo" component={Demo}/>
②存放位置不同:一般组件放在components中,而路由组件放在 pages
③ 接收到的props不同:
一般组件:写组件标签时传递了什么就能接收到什么
路由组件:接收到3个固定属性,history、location、match。各自里面又有一堆属性
history常用属性:go/goBack/goForward/push/replace
location常用属性: pathname/search/state
match常用属性: params/path/url
3、NavLink与封装NavLink
1、NavLink可以实现路由连接的高亮,通过activeClassName指定样式名
2、标签体内容是一个特殊的标签属性
3、通过this.props.children可以获取标签体内容
export default class MyNavLink extends Component {
render() {
return (
<NavLink activeClassName="piaoran" className="list-group-item" {...this.props}/>
)
}
}
4、Switch的使用
注册路由 Switch 匹配到一个路径就停止匹配其他了:这里两个/home,匹配上路第一个就不再匹配第二个了
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Fuck} />
<Route path="/home" component={Home} />
</Switch>
5、多路径(/atguigu/about)时样式加载问题
原因它加载时会把多级路径的前面那个(/atpingan)认为是请求路径(在使用的情况下),也就是请求css路径变为了localhost:3000/atpingan/css/bootstrap.css
解决办法:
多路径(/atpingan/about)时样式加载问题:
1.在public/index.html中引入样式时,不写./ 写 /(常用)
2.在public/index.html中引入样式时,不写./ 写 %PUBLIC_URL%(常用:仅限于react脚手架)
3.使用HashRouter (不常用)因为#后面的它认为是hash值是前端资源不带给请求端口
6 模糊匹配与精准匹配
不能随便开启严格匹配,有时候无法匹配二级路由不写就是模糊匹配,只要
<MyNavLink to="/home/a/b">Home</MyNavLink>
的路径有path="/home"对应的就可以匹配即输入的路径必须包含要匹配得路径,且顺序要一致加了exact(或者exact={true})就是必须完全一样才能匹配
<MyNavLink className="list-group-item" to="/atpingan/about" a={1}>
About
</MyNavLink>
<MyNavLink className="list-group-item" to="/atpingan/home/a" b={2}>
Home
</MyNavLink>
<Switch>
<Route path="/atpingan/about" component={About} />
<Route exact path="/atpingan/home" component={Fuck} />
<Route path="/atpingan/home" component={Home} />
</Switch>
这里点击Home 会匹配到component={Home}这个组件,不会匹配到component={Fuck}
7 Redirect
重定向:
①谁都匹配不上的时候听从 Redirect的路径
②一加载进来就用Redirect给个默认的展示
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/home"/>
</Switch>
8 嵌套路由
注册嵌套路由:
1、注册子路由时要写上父路由的path( 例如:to=“/home/message”)
2、路由的匹配是按照注册路由的顺序进行的
App.jsx
{/*路由链接*/}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
<MyNavLink to="/fuck" >Fuck</MyNavLink>
{/*注册路由*/}
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/fuck" component={Fuck} />
<Redirect to="/home"/>
</Switch>
Home.jsx
render() {
return (
<div>
<h3>我是Home的内容</h3>
<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/message"/>
</Switch>
</div>
</div>
);
}
9 路由组件传递参数的三种方式
9.1 路由组件传递params参数
第一步:携带params参数传递
注意写法:to后面是js写法要用{}包起来,${item.id}是携带参数
{messageArr.map((item) => {
return (
<li key={item.id}>
{/*携带params参数 */}
<Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.id}{item.title}</Link>
</li>
);
})}
第二步:声明接收params参数(/:id 就对应上面的第一个参数,/:title对应第二个参数)
<Switch>
{/**生命接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail} />
</Switch>
第三步:接收方组件接收参数(例如上面指定的component={Detail},Deatil就是指定接收参数方)
const {id,title} = this.props.match.params
9.2 路由组件传递search参数
第一步:向路由组件传递search参数
{/**向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${item.id}&title=${item.title}`}>{item.id}{item.title}</Link>
第二步:无需声明 正常注册路由组件即可
<Switch>
{/**无需声明 正常注册路由组件即可 */}
<Route path="/home/message/detail" component={Detail} />
</Switch>
第三步:接收方组件接收参数
①引入包 import qs from ‘querystring’
重点:querystring原本是Node.js自带的库,再新版本中已被弃用,在名称上有所调整,功能基本没有太大变化
import qs from ‘qs’
② 接收search参数:const { search } = this.props.location
此时接收到的参数是 ?id=18&title=“张三” 这种形式。
③截取并转换 search.slice(1)是截取第2个到最后
const { id, title } = qs.parse(search.slice(1))
9.3 路由组件传递state参数
第一步 向路由组件传递state参数,to里面是传递对象
<Link to={{ pathname: "/home/message/detail", state: { id: item.id, title: item.title }, }} >
第二步 接收state参数:无需声明 正常注册路由组件即可
<Route path="/home/message/detail" component={Detail} />
第三步 接收方组件接收参数
const { id, title} = this.props.location.state||{}
10 路由跳转的两种模式 replace与push
replace={true}
默认是push模式,开启replace模式的方法:
① replace={true}
② push模式是点击回退(<-)就回到上一次的页面,如果开启replace模式那么就不能回到上次页面
<Link replace={true} to={{ pathname: '/home/message/detail/', state: { id: msgObj.id, title: msgObj.title } }} {msgObj.title}</Link>
11、编程式路由导航
replace方式实现编程式路由导航(有param,search,state三种方式):
第一步 传递参数
//1.携带param参数:编写一段代码,让其实现跳转到Detail,且为replace跳转
//this.props.history.replace(/home/message/detail/${id}/${title}
);
//2.携带search参数:编写一段代码,让其实现跳转到Detail,且为replace跳转
//this.props.history.replace(/home/message/detail/?id=${id}&title=${title}
);
//3.携带state参数:编写一段代码,让其实现跳转到Detail,且为replace跳转
this.props.history.replace(/home/message/detail
,{id,title});
第二步 注册路由
{/*携带params参数 */}
{/* <Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.id}{item.title}</Link> */}
{/**向路由组件传递search参数 */}
{/* <Link to={`/home/message/detail/?id=${item.id}&title=${item.title}`}>{item.id}{item.title}</Link> */}
{/**向路由组件传递state参数 */}
<Link replace={true} to={{ pathname: "/home/message/detail", state: { id: item.id, title: item.title }, }} > {item.id} {item.title} </Link>
第三步接收
//1.接收params参数
// const {id,title} = this.props.match.params
//2.接收search参数
//const { search } = this.props.location
//search.slice(1)是截取第2个到最后,因为获取到的值是 ?id=18&title="张三"
//const { id, title } = qs.parse(search.slice(1))
//3.接收state参数
const { id, title} = this.props.location.state||{}
push方式实现编程式路由导航(有param,search,state三种方式):
第一步
1.编写一段代码,让其实现跳转到Detail,且为push跳转
//this.props.history.push(`/home/message/detail/${id}/${title}`);
//2.携带search参数:编写一段代码,让其实现跳转到Detail,且为push跳转
//this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`);
//3.携带state参数:编写一段代码,让其实现跳转到Detail,且为push跳转
this.props.history.push(`/home/message/detail`,{id,title});
第二第三步与replace一致
history中的属性:
回退:this.props.history.goBack();
前进 this.props.history.goForward();
指定前进或后退N步(N可为负) this.props.history.go(N)
12 withRouter 把一般组件转换为路由组件
一般组件(例如这里的Header)想要使用路由组件的属性 例如: this.props.history
- 需要:①把类的export default去掉 ②在类最底写上export default withRouter(Header)
- 解释:withRouter可以加工一般组件,让一般组件具有路由组件所特有的API,路由组件返回的是一个新组件
class Header extends Component {
back = () => {
this.props.history.goBack();
};
forward = () => {
this.props.history.goForward();
};
go = () => {
this.props.history.go(-2);
};
render() {
return (
<div>
<div className="page-header">
<h2>React Router Demo</h2>
</div>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go按钮</button>
<hr />
</div>
);
}
}
export default withRouter(Header)
13 BrowserRouter与HashRouter的区别
1、底层原理不一样
BrowserRouter使用的是H5 的history API不兼容IE9及一下版本,HashRouter使用的是URL的哈希值。
2、path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter 的路径包含#,例如:localhost:3000/#/demo/test
3、刷新后对路由state参数的影响
①BrowserRouter没有任何影响,因为state保存在history对象中
②HashRouter刷新后会导致路由state参数的丢失
4、HashRouter可以用来解决一些路径错误的相关问题(比如样式丢失的解决办法其中之一就是使用HashRouter)
5、扩展组件
1、懒加载
①引入包 import React, { Component, lazy,Suspense } from “react”;
②引入组件 (不能用以前的引入方式)
const Home = lazy(() => import("./Home/index"));
const About = lazy(()=> import("./Home/index"))
③注册路由时用Suspense包裹
<Suspense fallback={<Loading/>}>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/home" />
</Suspense>
④必须写fallback
这是自定义Loading组件(切记Loading组件的引入不能再是lazy模式)
import Loading from '../2_lazaLoad/loading/index'
export default class Loading extends Component {
render() {
return (
<div>
<h1 style={{ backgroundColor: "grey", color: "orange" }}>
Loading…………………………
</h1>
</div>
);
}
}
2、函数式组件使用状态 useState
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
3、副作用函数 useEffect
触发副作用钩子 useEffect ①初始化进来 ②第二个参数(即数组[]中的数据变化时)散③卸载组件时
卸载组件会触发useEffect里面的回调(即这里的return的函数)
import React, { useState, useEffect } from "react";
import ReactDOM from 'react-dom'
function useEffectHooks() {
const [count, setCount] = useState(0);
useEffect(() => {
let timer = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
function add(){
setCount(count=>count+1)
}
//卸载组件
function unmount(){
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
return (
<div>
<h1>当前求和为:{count}</h1>
<button onClick={add}>fuck me</button>
<button onClick={unmount}>卸载</button>
</div>
);
}
export default useEffectHooks;
4、useRef的神奇用法
在函数式组件中
①在函数里面定义 const myRef = React.useRef(null);
②在需要获取的组件上定义ref
<input type="text" ref={myRef}/>
③ 获取打标签节点的值
myRef.current.value
常用之处:获取初始值、弹出抽屉、弹出模态框
例如:弹出模态框
①在符组件定义 const myRef = React.useRef(null);
②在父组件引用子组件上定义ref >
<About ref={myRef } />
③在点击弹出模态框时定义点击事件(openModal是来自子组件自己定义的)
onClick={
myRef .current.openModal;
}
④子组件中定义,即这里所谓的 About组件
useImperativeHandle(ref,()=>{
openModal;
})
const openModal=()=>{
//这里定义打开模态框 例如: open=true 模态框的open属性指定这里的open。。。。就可以打开模态框
}
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。useImperativeHandle 应当与
forwardRef 一起使用:
export default forwardRef(FancyInput);