React学习
React 的特点
- 采用组件化的模式,声明式编码,提高开发效率及组件复用率
React创建虚拟dom的两种方式
-
jsx 创建虚拟dom
<script type="text/babel"> // 创建虚拟dom const VDOM = ( <h1 id="title"> <span>你好,React</span> </h1> ) // 渲染虚拟dom到页面 ReactDOM.render(VDOM, document.getElementById("test")); </script>
-
js 创建虚拟dom 一般不用
<script type="text/javascript"> // 创建虚拟dom const VDOM = React.creactElemnet("h1",{id:'title'},'你好,React'); // 渲染虚拟dom到页面 ReactDOM.render(VDOM, document.getElementById("test")); </script>
虚拟dom和真实dom
<script type="text/babel">
// 创建虚拟dom
const VDOM = (
<h1 id="title">
<span>你好,React</span>
</h1>
)
// 渲染虚拟dom到页面
ReactDOM.render(VDOM, document.getElementById("test"));
console.log("虚拟dom", VDOM);
console.log(typeof VDOM);
console.log(VDOM instanceof Object);
/*
关于虚拟dom:
1. 本质上是Object类型的对象(一般对象)
2. 虚拟dom比较轻,真实dom比较重,因为虚拟dom是React内部在用,无需真实dom那么多的属性
3. 虚拟dom最终会被React转化为真实dom,呈现在页面上
*/
</script>
jsx 语法规则
<script type="text/babel">
const myid = "aTGUgu";
const mydata = "HeLLodfajslfjlas";
// 1.创建虚拟dom
const VDOM = (
<div>
<h2 className="title" id={myid.toLowerCase()}>
<span style={{ color: 'red', fontSize: '200px' }}>
{mydata.toLowerCase()}
</span>
</h2>
<h2><input type="text"></input></h2>
</div>
)
// 2. 渲染虚拟dom到页面
ReactDOM.render(VDOM, document.getElementById("test"));
/**
* 标签中有js表达式,用花括号包裹,花括号里面可以使用表达式
* 样式的类名指定不要用class,要用className
* 内联style使用驼峰命名法
* 虚拟dom必须只有一个根标签
* 标签必须闭合
* 标签首字母
* (1)若小写字母开头,则将改标签转为html中同名元素,若html中无同名元素,报错
* (2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错
*/
</script>
React 处理数组
注意:{}里面只能写表达式,不能写代码,如果写代码,必须返回参数
模块与组件
-
函数式组件
<script type="text/babel"> // 1. 创建函数组件 function Dome() { console.log(this);// 此处的this是undefined ,因为babel编译后开启了严格模式 return <h2>我是用函数定义的组件(适用于[简单组件的定义])</h2> } // 2. 渲染组件到页面 ReactDOM.render(<Dome />, document.getElementById("test")); </script>
类的概念
<script type="text/babel"> // 创建一个Person类 class Person { // 构造器方法 constructor(name, age) { // 构造器中的this是谁,this是类的实例对象 this.name = name this.age = age } // 一般方法, speak 被放在了原型对象上,供实例使用 // 通过person实例调用speak时,speak中的this就是person实例 speak() { console.log(`我叫${this.name},我的年龄是${this.age}`); } } // 创建一个person的实例对象 const p1 = new Person('tom', 18); const p2 = new Person('jerry', 19); p1.speak(); p2.speak(); // 继承 class Student extends Person { constructor(name, age, grade) { super(name, age); this.grade = grade; } speak() { console.log(`我叫${this.name},我年龄是${this.age},我读的是${this.grade}年级`); } study() { // 谁调用谁就是this console.log("我很努力地学习"); } } const s1 = new Student("付立翔", 21, "大二"); s1.speak(); s1.study(); </script>
类式组件
<script type="text/babel">
// 1. 创建类式组件
class MyComponent extends React.Component {
// render 放在MyComponent的原型对象上
render() {
return <h2>我是类定义的组件(适用于[复杂组件的定义])</h2>
}
}
// 2. 渲染组件
ReactDOM.render(<MyComponent />, document.getElementById("test"));
/**
* 1. React解析组件发现了MyComponent 组件
* 2. React 发现这是一个类组件,React new 出来一个MyComponent 组件实例,并通过组件实例来调用原型上的方法
* 3. React 通过返回的虚拟DOM,转换成真实DOM ,随后呈现在页面上
*/
</script>
state
<script type="text/babel">
// 1. 定义一个类组件
class Weather extends React.Component {
constructor(props) {
super(props);
// 初始化状态
this.state = {
isHot: false,
wind:"微风"
}
// 让自定义的this指向Weather的实例对象
this.demo = this.demo.bind(this);
}
// 读东西,做展示
render() {
// 读取状态
const {isHot,wind} = this.state
return <h1 onClick={ this.demo} >今天天气很{isHot ? '炎热' : '寒冷'}{wind}</h1>
}
// 点击状态发生改变
demo() {
// 严重注意:状态state 不可以直接更改,要借助一个内置的API去更改!!!内部的信息发生改变,是一种合并,不是替换
// this.state.isHot = !this.state.isHot;
console.log(this);
const isHot = this.state.isHot;
this.setState({isHot:!isHot})
console.log('标题别点击了');
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
state精简
<script type="text/babel">
// 1. 定义一个类组件
class Weather extends React.Component {
// 成员属性 , 初始化状态
state = {
isHot: false,
wind: "微风"
}
render() {
const { isHot, wind } = this.state
return <h1 onClick={this.demo} >今天天气很{isHot ? '炎热' : '寒冷'}{wind}</h1>
}
/**
* 注意箭头函数没有this,如果使用this会向上寻找this
*/
demo = () => {
console.log(this);
const isHot = this.state.isHot;
this.setState({ isHot: !isHot })
console.log('标题别点击了');
}
}
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
props
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<div id="test1"></div>
<div id="test2"></div>
<!-- 加载 React。-->
<!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
<script type="text/javascript" src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script type="text/javascript" src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script type="text/javascript" src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<!-- 后面一定要使用babel -->
<script type="text/babel">
// 创建组件
class Person extends React.Component {
render() {
const {name,sex,age} = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 渲染组件到界面
ReactDOM.render(<Person name="付立翔" sex="男" age="21" />, document.getElementById("test"));
ReactDOM.render(<Person name="黄晓明" sex="男" age="49" />, document.getElementById("test1"));
ReactDOM.render(<Person name="马云" sex="男" age="50" />, document.getElementById("test2"));
</script>
</body>
外部传参
// 创建组件
class Person extends React.Component {
render() {
const {name,sex,age} = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 渲染组件到界面
const p = {name:"flx",sex:"nan",age:"21"}
// ...p 这是展开运算符
/**
* 详解见下图
*/
ReactDOM.render(<Person {...p}/>, document.getElementById("test"));
ReactDOM.render(<Person name="黄晓明" sex="男" age="49" />, document.getElementById("test1"));
ReactDOM.render(<Person name="马云" sex="男" age="50" />, document.getElementById("test2"));
对类型进行限制
<script type="text/babel">
// 创建组件
class Person extends React.Component {
render() {
const { name, sex, age } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 对传递的属性名进行限制
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
dd: PropTypes.func,
};
// 如果没有传递一个属性值就设置一个默认值
Person.defaultProps = {
sex: "不男不女",
age: 18
}
// 渲染组件到界面
const p = { name: "1", age: 21 }
// ...p 这是展开运算符
ReactDOM.render(<Person {...p} dd={dd} />, document.getElementById("test"));
ReactDOM.render(<Person name="黄晓明" sex="男" age={49} />, document.getElementById("test1"));
ReactDOM.render(<Person name="马云" sex="男" age={50} />, document.getElementById("test2"));
function dd() {
console.log("我是一个大好人");
}
</script>
props简写方式
<script type="text/babel">
// 创建组件
class Person extends React.Component {
// 对传递的属性名进行限制
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
dd: PropTypes.func,
};
// 如果没有传递一个属性值就设置一个默认值
static defaultProps = {
sex: "不男不女",
age: 18
}
render() {
const { name, sex, age } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 渲染组件到界面
const p = { name: "1", age: 21 }
// ...p 这是展开运算符
ReactDOM.render(<Person {...p} dd={dd} />, document.getElementById("test"));
ReactDOM.render(<Person name="黄晓明" sex="男" age={49} />, document.getElementById("test1"));
ReactDOM.render(<Person name="马云" sex="男" age={50} />, document.getElementById("test2"));
function dd() {
console.log("我是一个大好人");
}
</script>
函数式组件可以使用props
function Person(props) {
const { name, sex, age } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
dd: PropTypes.func,
};
// 如果没有传递一个属性值就设置一个默认值
Person.defaultProps = {
sex: "不男不女",
age: 18
}
const p = { name: "1", age: 21 }
// ...p 这是展开运算符
ReactDOM.render(<Person {...p} dd={dd} />, document.getElementById("test"));
ReactDOM.render(<Person name="黄晓明" sex="男" age={49} />, document.getElementById("test1"));
ReactDOM.render(<Person name="马云" sex="男" age={50} />, document.getElementById("test2"));
function dd() {
console.log("我是一个大好人");
}
ref
-
使用回调方式创建ref
// 创建组件 class Demo extends React.Component { showDate = () => { const { input1 } = this; alert(input1.value); } showDate2 = () => { const { input2 } = this; alert(input2.value); } render() { return ( <div> <input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"></input> <button ref={c => this.btn = c} onClick={this.showDate}>点击我提示左侧的数据</button> <input ref={c => this.input2 = c} onBlur={this.showDate2} type="text" placeholder="失去焦点提示数据"></input> </div> ) } } // 渲染dom ReactDOM.render(<Demo />, document.getElementById("test"));
-
使用函数方式创建ref 注意版本问题,低版本没有这个函数 ,推荐使用这个,可以清晰的看见这个
// 创建组件 class Demo extends React.Component { myRef = React.createRef() myRef2 = React.createRef() showDate = () => { alert(this.myRef.current.value); } showDate2 = () => { alert(this.myRef2.current.value); } render() { return ( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"></input> <button ref={c => this.btn = c} onClick={this.showDate}>点击我提示左侧的数据</button> <input ref={this.myRef2} onBlur={this.showDate2} type="text" placeholder="失去焦点提示数据"></input> </div> ) } } // 渲染dom ReactDOM.render(<Demo />, document.getElementById("test"));
注意:尽量避免使用ref,如果事件本身的发起者和数据是一个可以通过enent.taget拿到
受控组件
<script type="text/babel">
// 创建组件
class Demo extends React.Component {
handleSubmit = (event) => {
event.preventDefault();// 阻止表单提交
}
saveFromDate = (dataType) => {
return (event)=> {
this.setState({[dataType]:event.target.value})
}
}
render() {
return (
<form action="" onSubmit={this.handleSubmit}>
用户名:<input type="text" onChange={this.saveFromDate("username")} name="username" />
密码:<input type="password" onChange={this.saveFromDate("password")} name="password" />
<button type="submit">登录</button>
</form>
)
}
}
// 渲染组件
ReactDOM.render(<Demo />, document.getElementById("test"));
</script>
非受控组件
<script type="text/babel">
// 创建组件
class Demo extends React.Component {
username = React.createRef()
password = React.createRef()
handleSubmit = (event) => {
event.preventDefault();// 阻止表单提交
console.log(this.username.current.value+this.password.current.value);
}
render() {
return (
<form action="" onSubmit={this.handleSubmit}>
用户名:<input type="text" ref={this.username} name="username" />
密码:<input type="password" ref={this.password} name="password" />
<button type="submit">登录</button>
</form>
)
}
}
// 渲染组件
ReactDOM.render(<Demo />, document.getElementById("test"));
</script>
生命周期
<script type="text/babel">
// 创建组件
class Lift extends React.Component {
state = { opacity: 1 }
death = () => {
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById("test"));
}
// 组件挂载完毕
componentDidMount = () => {
this.timer = setInterval(() => {
let { opacity } = this.state;
opacity -= 0.05;
if (opacity <= 0) { opacity = 1; }
this.setState({ opacity });
}, 100);
}
// 组件将要卸载
componentWillUnmount = () => {
// 清除定时器
clearInterval(this.timer);
}
// 初始化渲染,状态更新之后
render() {
return (
<div>
<h2 style={{ opacity: this.state.opacity }}>学不会怎么办?</h2>
<button onClick={this.death}>不活了</button>
</div>
)
}
}
// 挂载到界面
ReactDOM.render(<Lift />, document.getElementById("test"));
</script>
组件的生命周期(旧)
<script type="text/babel">
class Demo extends React.Component {
// 第一次调用 :构造函数
constructor(props) {
console.log("Count---constuctor");
super(props)
// 初始化状态
this.state = {count:0}
}
// 第二次调用 :将要挂载的钩子
componentWillMount = ()=>{
console.log("Count---componentWillMount");
}
// 第三次调用 挂载完毕 : 挂载完毕的钩子
componentDidMount = ()=>{
console.log("Count---componentDidMount");
}
/*
* 更新的路线
*/
// 组件更新状态是否更新页面 || 默认返回true || 返回false不更新页面
shouldComponentUpdate = ()=>{
console.log("Count---shouldComponentUpdate");
return true;
}
// 组件将要更新
componentWillUpdate(){
console.log("Count---ComponentWillUpdate");
}
// 组件更新完毕
componentDidUpdate(){
console.log("Count---componentDidUpdate");
}
// 卸载组件之前调用钩子
componentWillUnmount= ()=>{
console.log("Count---componentWillUnmount")
}
/*
* 强制更新
*/
force = ()=>{
this.forceUpdate();
}
/*
* end: 最终卸载组件
*/
// 加1按钮的回调
add = () => {
this.setState({ count: this.state.count + 1 })
}
// 卸载组件
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById("test"))
}
// 状态信息
render() {
console.log("Count---render");
const { count } = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.add}>点我加一</button>
<button onClick={this.death}>点击我卸载组件</button>
<button onClick={this.force}>不更改状态,强制更新</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById("test"));
</script>
组件的生命周期全(旧)
<script type="text/babel">
// 创建组件
class Demo extends React.Component {
// 第一次调用 :构造函数
constructor(props) {
console.log("Count---constuctor");
super(props)
// 初始化状态
this.state = {count:0}
}
// 第二次调用 :将要挂载的钩子
componentWillMount = ()=>{
console.log("Count---componentWillMount");
}
// 第三次调用 挂载完毕 : 挂载完毕的钩子
componentDidMount = ()=>{
console.log("Count---componentDidMount");
}
/*
* 更新的路线
*/
// 组件更新状态是否更新页面 || 默认返回true || 返回false不更新页面
shouldComponentUpdate = ()=>{
console.log("Count---shouldComponentUpdate");
return true;
}
// 组件将要更新
componentWillUpdate(){
console.log("Count---ComponentWillUpdate");
}
// 组件更新完毕
componentDidUpdate(){
console.log("Count---componentDidUpdate");
}
// 卸载组件之前调用钩子
componentWillUnmount= ()=>{
console.log("Count---componentWillUnmount")
}
/*
* 强制更新
*/
force = ()=>{
this.forceUpdate();
}
/*
* end: 最终卸载组件
*/
// 加1按钮的回调
add = () => {
this.setState({ count: this.state.count + 1 })
}
// 卸载组件
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById("test"))
}
// 状态信息
render() {
console.log("Count---render");
const { count } = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.add}>点我加一</button>
<button onClick={this.death}>点击我卸载组件</button>
<button onClick={this.force}>不更改状态,强制更新</button>
</div>
)
}
}
// 定义父组件
class A extends React.Component{
// 初始化状态
state = {carName:"奔驰"}
changCar = ()=>{
let {carName} = this.state;
this.setState({carName : "奥拓"});
}
render(){
return(
<div>
<div>我是A组件</div>
<button onClick={this.changCar}>换车</button>
<B carName={this.state.carName} />
</div>
)
}
}
// 定义子组件
class B extends React.Component{
// 组件将要接收新的Props,默认接收props参数
componentWillReceiveProps(props){
console.log("B---componentWillReceiveProps",props)
}
shouldComponentUpdate(){
console.log("B---shouldComponentUpdate")
return true;
}
componentWillUpdate(){
console.log("B---componentWillUpdate")
}
componentDidUpdate(){
console.log("B---componentDidUpdate")
}
componentWillUnmount(){
console.log("B---componentWillUnmount")
}
render(){
return(
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}
ReactDOM.render(<A />, document.getElementById("test"));
</script>
getSnapshotBefroeUpdate
## <font color='red'>重点: react和vue中的key有什么作用</font>
1. 虚拟DOM中的key的作用:
<font color='orange'>1)**简单的说**:key是虚拟DOM对象的标识符,在更新显示时key起着极其重要的作用</font>
<font color='yellow'>2) **详细的说**:当状态中的数据发生变化的时候,react会根据**<font color='red'>[新数据]</font>**生成**<font color='red'>[新的虚拟DOM]</font>**,随后React进行**<font color='red'>[新虚拟DOM]</font>**与**<font color='red'>[旧虚拟DOM]</font>**的diff比较,比较规则如下:</font>
2. 旧虚拟DOM中找到了与新虚拟DOM想相同的key:
1).<font color='cornflowerblue'>若虚拟DOM中的内容</font><font color='green'>没变</font>,<font color='purple'>直接使用</font>之前的<font color='orange'>真实DOM</font>
2)<font color='cornflowerblue'>.若虚拟DOM中的内容</font><font color='green'>变了</font>,<font color='purple'>则生成</font>新的<font color='yellow'>真实的DOM,最后替换掉页面中之前的真实DOM</font>
3. 旧虚拟DOM中未找到与新虚拟DOM相同的key根据数据创建新的真实DOM,随后渲染到页面
# R eact 脚手架
## 相关命令
npm i create-react-app -g
1. yarn start -----------------------------------开启
2. yarn build -----------------------------------最终进行一次打包
3. yarn test ------------------------------------几乎不用,
4. yarn eject ----------------------------------react 怕你碰坏webpack的配置文件,所以都隐藏了,一旦暴露出来,就隐藏不回去了
![image-20210528213406713](https://gitee.com/fu-lixiang/flx-img/raw/master/img/image-20210528213406713.png)
## nanoid
添加方式 yarn add nanoid
```react
import {nanoid} from "nanoid"
nanoid(); // 每次生成一个唯一值
使用axios请求
getStudentData = () => {
axios({
method: 'get',
url: 'http://localhost:3000/students'
}).then(function (value) { console.log(value.data); }, function (reason) { console.log(reason); })
}
如果请求的不是3000端口的服务器,配置中间服务器
// 在package.json中
加入一条 , 端口号5000根据服务器的端口改
"proxy": "http://localhost:5000"
配置多个代理服务器
// 在pulic中新建setupProxy.js
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('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: { '^/api2': '' }
})
)
}
消息订阅与发布机制
使用
npm install pubsub-js --save
import PubSub from ‘pubsub-js’// 引入
const PubSub = require(“pubsub-js”); // comment.js 引入
消息发布
PubSub.publish('delete',data) // 发布消息
消息订阅
var token = PubSub.subscribe('delete',function(_,data){});// 订阅
取消订阅
PubSub.unsubscribe(token)
React 路由
1. 什么是路由
-
一个路由就是一个映射关系(key:value)
-
key 是路径,value可能是function 或 component
路由的原理
根据浏览器的history
因为浏览器的结构是一个栈的结构
BrowserRouter
<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>
<BrowserRouter> {/* 包在最外层 */}
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生HTML中,使用a标签来跳转到不同的页面 */}
{/* <a className="list-group-item active" href="./about.html">About</a>
<a className="list-group-item" href="./home.html">Home</a> */}
{/* 在React中靠路由连接实现切换组件 */}
<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>
</div>
简化
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>
, document.getElementById('root'))
切换 hash route
import React from 'react'
import ReactDOM from 'react-dom'
import { HashRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render(
<HashRouter>
<App />
</HashRouter>
, document.getElementById('root'))
路由组件传递数据
路由组件与一般组件
-
写法不同
一般组件:
路由组件:
-
存放位置不同
一般组件: components
路由组件: pages
-
接收的props 不同
一般组件: 写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性
NavLink
添加这个自动给链接加一个类action
设置样式 activeClassName
<NavLink activeClassName="demo" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="demo" className="list-group-item" to="/home">Home</NavLink>
NavLink 的封裝
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return (
<NavLink activeClassName="demo" className="list-group-item" {...this.props} ></NavLink>
)
}
}
{/* children 这个是在标签内的文字 */}
<MyNavLink to="/home" children="dddd" title="dddd"></MyNavLink>
注册的路由特别多的解决办法
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={About} />
</Switch>
一个以上包裹Switch
只匹配一次,不会继续向下匹配
import { NavLink, Route,Switch } from 'react-router-dom'
多级路由样式丢失的问题
查找丢失的css,把.去掉
或者修改路由模式为hashRouter
路由的模糊匹配和精准匹配
/home/a/b
home a b
/home
使用精准配置 需要 慎用 没有影响不要乱开 有可能导致无法匹配二级路由
redirect
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={About} />
{/* 兜底的 */}
<Redirect to="/about" />
</Switch>
嵌套路由的使用
// 嵌套路由 在一个路由组件中又有一个路由组件
注意匹配模式
例如: /home/news 先匹配home,返回home,在匹配news,返回news,如果使用精准匹配就会出错
路由组件传递params参数
render() {
const { messageArr } = this.state;
return (
<div>
<ul>
{
messageArr.map((msgObj) => {
return (
/* 向路由组件传递params参数 */
<li key={msgObj.id}><Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> </li>
)
})
}
</ul>
{/* 声明接收 */}
<Route path="/home/message/detail/:id/:title" component={Detail} />
</div>
)
}
// 拿
const {id,title} = this.props.match.params;
路由组件传递search参数
/* 向路由组件传递search参数 */
<Link to={`/home/message/detail?id=${msgObj.id}&title=${msgObj.title}`}></Link>
{/* search参数无需声明接收 */}
<Route path="/home/message/detail" component={Detail} />
// 导入querystring
import qs from 'querystring'
// 将json转dd=dd&cc=cc
qs.stringfiy(obj)
//反转
qs.parse(str)
// 接收
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
路由组件传递state参数
{/* 向路由传递state参数 */}
<Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}></Link>
{/* state参数无需声明接收 */}
<Route path="/home/message/detail" component={Detail} />
// 接收state参数
const {id,title} = this.props.location.state
// 注意:如果缓存和历史记录清除之后就会取不到值,因为state是保存在浏览器的history里
// 防止保错
const {id,title} = this.props.location.state || {};
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
}) || {};
push 和 replace 痕迹
replace 没有历史记录,只是替换
如果点击图片实现跳转
编程式路由导航
push 和 replace
<button onClick={()=>{this.pushShow(msgObj.id,msgObj.title)}}>push查看</button>
<button onClick={()=>{this.replaceShow(msgObj.id,msgObj.title)}}>replace查看</button>
replaceShow = (id, title) => {
// 使用params
// this.props.history.replace(`/home/message/detail/${id}/${title}`);
// 使用search
// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`);
// 使用state
this.props.history.replace(`/home/message/detail`, { id, title });
}
pushShow = (id, title) => {
// 使用params
// this.props.history.push(`/home/message/detail/${id}/${title}`);
// 使用search
// this.props.history.push(`/home/message/detail?id=${id}&title=${title}`);
// 使用state
this.props.history.push(`/home/message/detail`, { id, title });
}
前进后退
back = () => {
this.props.history.goBack();
}
forward = () => {
console.log(this.props);
this.props.history.goForward();
}
// go
// 后退一步
this.props.history.go(-1);
// 后退两步
this.props.history.go(-2);
// 前进一步
this.props.history.go(1);
// 前进两步
this.props.history.go(2);
自动跳转
componentDidMount() {
setTimeout(() =>{ this.props.history.push('/home/message') },2000);
在一般组件中使用路由组件api
import {withRouter} from 'react-router-dom'
export default withRouter(组件)
BrowserRouter 和 HashRouter 的区别
antd
安装
yarn add antd
修改默认样式
redux
能不用就不用,复杂的组件嵌套关系可以使用
yarn add redux
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
/src/redux/store.js
// 引入createStore, 专门用于创建redux中最为核心的store对象
import {createStore} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'
export default createStore(countReducer);
/*
1. 该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
2. reducer函数会接到两个函数,分别为:之前的状态(preState),动作对象(action)
*/
/src/redux/count_reducers
const initState = 0;
export default function countReducer(previousState = initState, action) {
const { type, data } = action;
// 根据type决定如何加工数据
switch (type) {
case 'increment':
previousState + data; // 如果是加
case 'decrement':
previousState - data; // 如果是减
default:
return previousState;
}
}
注意:每次更改数据不会引起重绘,加入一个store检测是否更新,如果更新就重绘
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store'
ReactDOM.render(
// 可以检查React组件有没有错误
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// 检查有没有改变,如果有改变就重绘重排
store.subscribe(() => {
ReactDOM.render(
// 可以检查React组件有没有错误
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
});
-
理解:
reducer本质是一个函数,接收previousState,action
reducer初始化,加工
reducer被第一次调用时,是store自动完成的
传递的previousState是undefined
传递的action:{type:@@REDUX/INIT_a.2.b.3}
在index.js中监测store中状态的改变,一旦发送改变重新渲染
备注:数据改变不刷新页面,需要自己写数据改变刷新页面
/*
该文件专门为Count组件生成action对象
*/
/src/redux/count_action.js
export const createIncrementAction = value => ({ type: "add", value })
export const createDecrementAction = value => ({ type: "decrement", value })
react 执行异步任务
不是必须要用的,看应用场景
yarn add redux-thunk
需要使用中间件来转换
/src/redux/store
+ import {createStore,applyMiddleware} from 'redux'
import Count from './count_reducers'
// 引入redux-thunk,用于支持thunk
import thunk from 'redux-thunk'
+ export default createStore(Count,applyMiddleware(thunk));
// 异步是一个函数
export const yibujiazai = (data, time) => {
return () => { setTimeout(() => { store.dispatch(add(data)) }, time) }
}
react-redux
yarn add react-redux
定义容器组件
/src/containers/Count/index.jsx
import CountUI from "../../components/Count"
// 引入connect 用于连接UI组件与redux
import {connect} from "react-redux"
// 连接ui组件
export default connect()(CountUI)
// 通过父组件进行传递
import React, { Component } from 'react'
import Count from './containers/Count'
// 引入store
import store from "./redux/store"
export default class App extends Component {
render() {
return (
<div>
<Count store={store}></Count>
</div>
)
}
}
通过connect连接ui组件
前面的括号是两个函数,后面是连接的ui组件名称
前面的函数传递的是状态,后面的函数传递的是操作状态的方法
connect (a,b)(UI)
function a(state) {
// state 是初始状态,store.getState()
return {
n: {count:state}
}
}
function b(dispatch) {
// dispatch 是分发状态
return {
jia: number => dispatch(add(number)),
jian:number=>dispatch(jian(number)),
yubu:(number,time)=>dispatch(yibujiazai(number,time))
}
}
// 精简模式 , dispatch自动分发
export default connect(state => ({ n: { count: state } }), ({
jia: add,
jian: jian,
yubu: yibujiazai
}
)
)(CountUI)
注意:如果使用了react-redux,不用监测action是否改变,删除以下,connect()()自动render,优化点一
// 监测redux中的状态改变,若redux中的状态发送了改变,那么重新渲染组件
store.subscribe(()=>{
ReactDOM.render(<App />, document.getElementById("root"));
})
provider
如果有以下这么多的组件,简化传递store
{/* 给容器组件传递store */}
<Count store={store}></Count>
<Demo1 store={store}></Demo1>
<Demo1 store={store}></Demo1>
<Demo1 store={store}></Demo1>
<Demo1 store={store}></Demo1>
<Demo1 store={store}></Demo1>
<Demo1 store={store}></Demo1>
<Demo1 store={store}></Demo1>
<Demo1 store={store}></Demo1>
<Demo1 store={store}></Demo1>
解决:
/src/index.js
import App from './App'
import React from 'react'
import ReactDOM from 'react-dom'
+ import store from './redux/store'
+ import { Provider } from 'react-redux'
ReactDOM.render(
+ <Provider store={store}>
<App />
+ </Provider>
, document.getElementById("root"));
优化containers 和 UI组件,将俩个文件合并为一个
/src/constainers/Count
import { connect } from "react-redux"
import { add, jian, yibujiazai } from "../../redux/count_action"
import React, { Component } from 'react'
class Count extends Component {
increment = () => {
console.log(this.props);
this.props.jia(1);
}
jian = () => {
this.props.jian(1);
}
jijia = () => { }
yibu = () => {
this.props.yubu(1, 500);
}
render() {
return (
<div>
<div>{this.props.pre.count}</div>
<button onClick={this.increment}>加一</button>
<button onClick={this.jian}>减一</button>
<button onClick={this.jijia}>奇数加一</button>
<button onClick={this.yibu}>异步加载</button>
</div>
)
}
}
export default connect(state => ({ pre: { count: state } }), ({
jia: add,
jian: jian,
yubu: yibujiazai
}
)
)(Count)
优化总结
数据共享
/redux/actions/count.js
/redux/reducers/count.js
store管理俩个组件
import { createStore, applyMiddleware, combineReducers } from 'redux'
import Count_reducer from './reducers/count'
import Person_reducer from './reducers/person'
import thunk from 'redux-thunk'
// 汇总所有的reducer变成一个reducer
const allReducer = combineReducers({
he: Count_reducer,
rens: Person_reducer
});
export default createStore(allReducer, applyMiddleware(thunk))
state变成总对象
export default connect(
state => ({yiduiren:state.rens})
)(Person);
改变连接的值
export default connect(
state => ({ yiduiren: state.rens, data: state.he }),
{
jiaYiRen: AddPerson,
}
state 是redux全局保存的
// 获取值
this.props.yiduiren
this.props.data
注意:多个组件reducers中type的值不能一样,不然会执行两次,
注意:对数组进行操作时,使用[…],使用数组unshift并不会render,因为redux对数组进行的是浅比较,比较的是地址值
redux调试工具
yarn add redux-devtools-extension
// 在store中
// 引入redux-devtools-extension
import { composeWithDevTools } from 'redux-devtools-extension'
// 汇总所有的reducer变成一个reducer
const allReducer = combineReducers({
he: Count_reducer,
rens: Person_reducer
});
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
项目打包
npm i serve -g
yarn global add serve
serve build