一、React入门
1.1.1、官网
1)英文官网:https://reactjs.org/t
2)中文官网: React 官方中文文档 – 用于构建用户界面的 JavaScript 库
1.1.2、介绍
React 是一个用于构建用户界面的JavaScript库 用户界面:HTML页面(前端) React主要用来写HTML页面,或构建Web应用 如果从MVC的角度来看,React仅仅是视图层(V),也就是只负责视图的渲染,而并非提供了完整的M和C的功能 React 起源于Facebook的内部项目,后又用来架设Instagram的网站,并于2013年5月开源
1.1.3、React特点
1)声明式编码
2)组件化编码
3)React Native编写原生应用
4)高效(优秀的Diffing算法)
1.1.4、高效之处
1)它使用虚拟(virtual)DOM,不总是直接操作页面的真实DOM
2)DOM Diffing算法,最小化页面重绘
1.2、React的基本使用
1.2.1、创建虚拟DOM
const vdom = <h1>hello,react</h1>; //创建
ReactDOM.render(vdom, document.getElementById('app')(容器)); //渲染
const vdom = React.createElement('h1', { id: 'title' }, 'hello,react'); //创建(了解即可)
ReactDOM.render(vdom, document.getElementById('app')(容器)); //渲染
关于虚拟dom:
1、本质是object类型的对象
2、虚拟dom方法少,真实dom方法多
3、虚拟dom会被react转换为真实dom
1.3、React JSX
1.3.1、JSX语法规则
const ids = 'llll';
const ele = (
<h2 className='dddd' id={ids}>
555
<span style={{ color: 'white' }}></span>
</h2>
);
ReactDOM.render(vdom, document.getElementById('app')(容器)); //渲染
1、定义虚拟dom时不用引号
2、标签中使用js表达式时使用{}
3、样式的类名要用className
4、内联样式要用style={{color:“white”}}
5、虚拟dom只有一个根标签
6、标签必须闭合
7、标签首字母
1)若为小写则转化为html中同名元素,其余则报错
2)若为大写则渲染对应组件,未定义则报错
1.4、模块与组件、模块化与组件化的理解
1.4.1、模块
1)理解:向外提供特定功能的js程序,一般就是一个js文件 2)为什么号拆成快:阳有业务逻排增加,代码越来越多且复杂 3)作用:复用js,简化js的编写,提高js运行效率
1.4.2、组件
1)理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等) 2)为什么:一个界面的功能更复杂 3)作用:复用编码,简化项目编码,提高运行效率名
1.4.3、模块化
当应用的js 都以换块来编写的,这个应用就是一个模块化的应用·
1.4.4、组件化
当应用是以多组件的方式实现,这个应用就是一个维件化的应用
二、React面向组件编程
2.1、创建组件两种方式
2.1.1函数式组件:
function Demo() {
return <h1>44555555</h1>;
}
ReactDOM.render(<Demo />, document.getElementById('app')); //渲染
2.1.2类式组件:
class Demo extends React.Component {
render() {
return <h2>5555555555</h2>;
}
}
ReactDOM.render(<Demo />, document.getElementById('app')); //渲染
2.2、state
2.2.1、state写法
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
iss: true,
};
}
render() {
return <h2>{this.state.iss ? '男' : '女'}</h2>;
}
}
ReactDOM.render(<Demo />, document.getElementById('app')); //渲染
2.2.2、绑定事件
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
iss: true,
};
//解决this指向问题
this.demo = this.demo.bind(this);
}
demo() {
//demo放在哪里?Weather的原型对象上,供实例使用
//由于demo是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式、所以demo中的this为undefined
console.log(this);
}
render() {
return <h2 onClick={this.demo}>{this.state.iss ? '男' : '女'}</h2>;
}
}
ReactDOM.render(<Demo />, document.getElementById('app')); //渲染
2.2.3、修改state数据
使用setstateAPI更新state里面的数据,执行合并操作
constructor:仅仅调用一次
render:1+n次,1:初始化,n:点击的次数
标准语法如下:
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
iss: true,
};
//解决this指向问题
this.demo = this.demo.bind(this);
}
demo() {
//demo放在哪里?Weather的原型对象上,供实例使用
//由于demo是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式、所以demo中的this为undefined
this.setstate({ iss: !this.state.iss }); //调用setstateAPI
}
render() {
return <h2 onClick={this.demo}>{this.state.iss ? '男' : '女'}</h2>;
}
}
ReactDOM.render(<Demo />, document.getElementById('app')); //渲染
简写语法:
class Demo extends React.Component {
state = {
iss: true,
};
demo = () => {
this.setstate({ iss: !this.state.iss }); //调用setstateAPI
};
render() {
return <h2 onClick={this.demo}>{this.state.iss ? '男' : '女'}</h2>;
}
}
ReactDOM.render(<Demo />, document.getElementById('app')); //渲染
2.3、Props
2.3.1、props基本使用
class Demo extends React.Component {
render() {
return <h2>{this.props.name}</h2>;
}
}
ReactDOM.render(<Demo name='tom' />, document.getElementById('app')); //渲染
ReactDOM.render(<Demo name='John' />, document.getElementById('app'));
2.3.2、批量传值
class Demo extends React.Component {
render() {
return <h2>{this.props.name}</h2>;
}
}
const k = { name: 'lll', age: '18', height: '185' };
ReactDOM.render(<Demo {...k} />, document.getElementById('app')); //渲染
2.3.3、传值限制
15.5版本已经弃用
class Demo extends React.Component {
render() {
return <h2>{this.props.name}</h2>;
}
static propTypes = {
name: React.PropTypes.string,
};
}
ReactDOM.render(<Demo name='tom' />, document.getElementById('app')); //渲染
16版本以上:
npm install prop-types//安装
import PropType from 'prop-types';
class Demo extends React.Component {
render() {
return <h2>{this.props.name}</h2>;
}
static propTypes = {
name: PropTypes.string.isrequired,
age: PropTypes.string,
};
static defaultProps = {
sex: '男',
};
}
ReactDOM.render(<Demo name='tom' />, document.getElementById('app')); //渲染
props:只读不可修改
2.3.4、函数式组件props
function Demo(props) {
return <h2>{props.name}</h2>;
}
ReactDOM.render(<Demo name='tom' />, document.getElementById('app')); //渲染
props总结:
1、批量传输使用{...p}
2、传值限制给组件加propsTypes或者defaultProps
3、props不可修改
2.5、refs
2.5.1、字符串形式的ref(避免使用)
如果你之前使用过 React,你可能了解过之前的 API 中的 string 类型的 ref 属性,例如 "textInput"
。你可以通过 this.refs.textInput
来访问 DOM 节点。我们不建议使用它,因为 string 类型的 refs 存在 一些问题。它已过时并可能会在未来的版本被移除。
class Demo extends React.Component {
click = () => {
console.log(this.refs.inp);
};
render() {
return <h2 ref='inp'>{this.props.name}</h2>;
}
}
ReactDOM.render(<Demo />, document.getElementById('app')); //渲染
2.5.2、回调形式的ref
从this中取数据,因为箭头函数中的this指向Demo。
如果 ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
内联写法:
class Demo extends React.Component {
click = () => {
console.log(this.h2);
};
render() {
return (
<h2
ref={(e) => {
this.h2 = e;
}}
>
{this.props.name}
</h2>
); //e为当前节点:h2
}
}
ReactDOM.render(<Demo />, document.getElementById('app')); //渲染
h2:是当前ref的名字
解决上述问题:通过class的绑定函数的方式(无关紧要的知道即可)
class Demo extends React.Component {
click = () => {
console.log(this.h2);
};
h2s = (c) => {
console.log(c);
};
render() {
return (
<h2 onClick={this.click} ref={this.h2s}>
{this.props.name}
</h2>
);
}
}
ReactDOM.render(<Demo />, document.getElementById('app')); //渲染
2.5.3、createRefAPI
注:createRef专人专用,如果有多个可创建多个
class Demo extends React.Component {
myref = React.createRef();
h2s = (c) => {
console.log(this.myref.current);
};
render() {
return (
<h2 onClick={this.click} ref={this.myref}>
{this.props.name}
</h2>
);
}
}
ReactDOM.render(<Demo />, document.getElementById('app')); //渲染
2.5.4、ref总结
1、字符串ref,简单,已使用,非条件不允许情况下尽量避免
2、回调ref,绑定class方式:繁杂,需单独写方法,内联写法简单
3、使用createRef API最为复杂的一种也是react最为推荐的一种
2.5.5、事件处理
(1).通过onXxx属性指定事件处理函数(注意大小写)-------onclick--onClick a.React使用的是自定义(合成)事件,而不是使用的原生DOM事件------为了更好的兼容性 b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)--------为了高效 (2).通过event,target得到发生事件的DOM元素对象----------不要过度使用Ref
2.6、表单数据
2.6.1、非受控组件
现用现取:什么时候需要什么使用获取
class Test extends React.Component {
handlesubmit = (e) => {
e.preventDefault(); //组织默认事件
console.log(this.username.value);
};
render() {
return (
<form onSubmit={this.handlesubmit}>
<input ref={(c) => (this.username = c)} name='username' type='text' />
</form>
);
}
}
2.6.2、受控组件
自动获取:不需要你调用自己获取
class Test extends React.Component {
state = {
username: '',
};
handlesubmit = (e) => {
e.preventDefault(); //组织默认事件
console.log(this.username.value);
};
changeinput = (e) => {
console.log(e.target);
this.setState({
username: e.target.value,
});
};
render() {
return (
<form onSubmit={this.handlesubmit}>
<input onChange={changeinput} ref={(c) => (this.username = c)} name='username' type='text' />
</form>
);
}
}
2.7、高阶函数和函数的柯里化
2.7.1、高阶函数
若函数的参数是一个函数,那么该函数是一个高阶函数
若函数的返回值是一个函数,那么该函数是一个高阶函数
class Test extends React.Component {
state = {
username: '',
password: '',
};
handlesubmit = (e) => {
e.preventDefault(); //组织默认事件
console.log(this.state.username);
console.log(this.state.password);
};
changeinput = (inpType) => {
return (e) => {
this.setState({
[inpType]: e.target.value,
});
};
};
render() {
return (
<form onSubmit={this.handlesubmit}>
<input onChange={this.changeinput('username')} name='username' type='text' />
//此处将函数的返回值作为change的回调函数
<input onChange={this.changeinput('password')} name='password' type='text' />
</form>
);
}
}
2.7.2、函数的柯里化
通过函数调用,继续返回函数的方式,实现多次接收参数,最后统一处理的函数编码形式
function sum(a) {
return (b) => {
return (c) => {
return a + b + c;
};
};
}
sum(1)(2)(3); //6
高阶函数中的changeinput就是利用了函数的柯里化
2.8、组件的生命周期
2.8.1、componentWillMount(){}
页面将要挂载时调用{}
class Test extends React.Component{
componentWillMount() {}
}
2.8.2、render(){}
常用渲染,用于将组件挂载到页面
class Test extends React.Component{
render() {}
}
2.8.3、componentDidMount(){}
页面挂载完毕时调用{}
class Test extends React.Component{
componentDidMount() {}
}
2.8.4、componentWillUnmount(){}
页面即将卸载的时候调用{}
class Test extends React.Component{
componentWillUnmount() {}
}
2.8.5、shouldComponentUpdate(){}
控制组件更新的阀门:默认是打开(true),false关闭,调用setState()时会调用shouldComponentUpdate(){}
class Test extends React.Component{
shouldComponentUpdate() {}
}
2.8.6、componentWillUpdate(){}
组件将要更新时调用
class Test extends React.Component {
componentWillUpdate() {}
}
2.8.7、componentDidUpdate(){}
组件更新完毕时调用
三个参数
1、prevProps:更新完成前上一次传值的内容
2、prevState:更新完成前上一次state的内容
3、snapshotValue:拿到getSnapshotBeforeUpdate的返回值内容
class Test extends React.Component {
componentDidUpdate() {}
}
forceUpdate()强制更新
class Test extends React.Component {
test = () => {
this.forceUpdate();
};
}
注:强制更新时不走shouldComponentUpdate!!!
2.8.8、componentWillReceiveProps(){}
父组件给子组件传值时调用(第一次不算)
class Test extends React.Component {
componentWillReceiveProps() {}
}
2.8.9、总结React旧生命周期
1,初始化阶段:由ReactDOM.render()触发---初次渲染
1.constructor()
2,componentWillMount()
3.render()
4.componentDidMount()
2,更新阶段:由组件内部this.setSate()或父组件重新render触发
1.shouldComponehtUpdate()
2.componentwillUpdate()
3.render()
4.componentDidUpdate()
3,卸载组件:由ReactDON.unmountComponentAtNode()触发
1.componentWi11Unmount()
2.8.10、react18版本新生命周期
在旧的生命周期基础上进行修改,修改如下(不推荐使用,未来可能废弃)
UNSAFE_componentWillMount()
UNSAFE_componentWillReceiveProps()
UNSAFE_componentwillUpdate()
用法不变只是将名字修改
增添了两个生命周其函数
1)getDerivedStateFromProps(){}
必须有return :可以是null和状态对象
可以接收值,接收props和state值
props:组件接收的值
state:组件内state值
用法:state的值在任何时候都取决于props
class Test extends React.Component {
static getDerivedStateFromProps(props, state) {
return null;
}
}
2)getSnapshotBeforeUpdate(){}
必须有return :可以是null和snapshotValue对象
class Test extends React.Component {
getSnapshotBeforeUpdate() {
return null;
}
}
小案例
class Test extends React.Component {
state = { newsArr: [] };
componentDidMount() {
setInterval(() => {
//创建定时器每一秒增加一条新闻
let news = '新闻' + (this.state.newsArr.length + 1);
this.setState({ newsArr: [news, ...newsArr] });
}, 1000);
}
getSnapshotBeforeUpdate() {
//获取更新前的元素高度
return this.list.scrollHeight;
}
componentDidUpdate(prevprops, preostate, prevheight) {
//接收到更新后的高度
this.list.scrollTop += this.list.scrollHeight - prevherght; //获取差值,回调形式的ref从当前实例拿值
}
render() {
return (
<div
className='list'
ref={(c) => {
this.list = c;
}}
>
//回调形式的ref
{this.state.newsArr.map((n, index) => {
return <div key={index}>{n}</div>;
})}
</div>
);
}
}
2.8.11、总结新生命周期
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()=====〉常用,一般在这个钩了中做一些收尾的事,例如;关闭定时器、取消订阅消息
2.9、Diffing算法
算法对比的最小力度是标签
1.在遍历过程中,key的作用
1)简单的说:是虚拟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,随后渲染到到页山 2.用index作为key可能会引发的问题
1,若对数据进行:逆序添加、逆序删除等破坏顺序操作会产生没有必要的真实DOM更新==〉界面效果没问题,但效率低(注意看)
2。如果结构中还包含输入类的DOM:会产生错误DOM更新==〉界面有问题
3,注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
3.开发过程中如何选择值
1、最好选择每条数据的唯一值做标识,作为key
2、如果只是简单的展示数据,用index也是没问题的
三、React脚手架
3.1、使用脚手架
3.1.1、create-react-app创建应用
1)全局安装:npm install -g create-react-app
2)创建项目:create-react-app my-item
3)进入项目:cd my-item
4)启动项目:npm start
3.1.2、文件解释
public---静态资源文件夹
faviconicon --网站页签图标
index.html --主页面:
logo192.png -- logo图
logo512.png --- logo图
manifestjson --应用加壳的配置文件
robots.txt -----爬虫协议文件·
src---源码文件夹
App.css-App 组件的样式
App.js-App 组件 App
.testjs -用于给App做测试
index.css-样式
indexjs --入口文件
logo.svg -----logo图
reportWebVitals.js--页面性能分析文件(需要 web-vitals库的支持)
setupTests.js----组件单元测试的文件(需要jest-dom库的支持)”
3.2、index.js书写步骤
1)引入react核心库:import React from 'react'
2)引入ReactDOM:import ReactDOM from 'react-dom'
3)引入App组件:import App from './App'
4)渲染App组件:ReactDOM.render(<App/>,document.getElementById('root'))
3.3、App.js书写步骤
创建外壳组件:import React from 'react'
创建组件:
class App extends React.Component {
render() {
return <div></div>;
}
}
export default App;
3.4、vscode快速编写插件
ES7 React/Redux/GraphQL/React-Native snippets(复制搜索即可)
rcc:一键创建类组件
import React, { Component } from 'react';
export default class FileName extends Component {
render() {
return <div>$2</div>;
}
}
rfc:一键创建函数组件
import React from 'react';
export default function $1() {
return <div>$0</div>;
}
3.5、功能界面的组件化编码流程
1.拆分组件:拆分界面,抽取组件 2.实现静态组件:使用组件实现静态页面效果 3.实现动态组件 3.1动态显示初始化数据 3.1.1数据类型 3.1.2数据名称 3.1.2保存在哪个组件? 3.2交互(从绑定事件监听开始)
3.6、子项父传值
子组件:
class B extends React.Component {
changeValue = (e) => {
this.props.increase(e.targrt.value);
};
render() {
return <input onBlur={this.changeValue} />;
}
}
父组件:
class A extends React.Component {
increase = (data) => {
console.log(data); //传过来的数据
};
render() {
return <B increase={this.increase} />;
}
}
3.7、服务器代理:
第一种:package.json文件修改,在最后添加下面代码
{
"proxy":"http://localhost:5000"
}
import axios from 'axios';
export default class A extends React.Component {
test = () => {
axios.get('http://localhost:3000/student').then(
//注意端口号是3000并不是5000
(response) => {
console.log(response.data);
},
);
};
render() {
return <button onClick={this.test}></button>;
}
}
第二种:建立文件:setupProxy.js:内容为:下面代码
const proxy = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
proxy('/api1', {
target: 'http://localhost:5000',
changeOrigin: true,
pathRewrite: { '^/api1': '' },
}),
);
};
import axios from 'axios';
export default class A extends React.Component {
test = () => {
axios.get('http://localhost:3000/api1/student').then(
(response) => {
console.log(response.data);
},
(err) => {
console.log(err.message);
},
);
};
render() {
return <button onClick={this.test}></button>;
}
}
target:请求转发地址
changeOrigin:控制服务器收到的请求头中的Host字段的值
true:收到的值为localhost:5000,
false:收到的值为locahost:3000
默认为false
pathRewrite:重写请求路径
3.8、兄弟组件传值
PubSubjs(适用于任何组件间的传值)
npm install pubsub-js --save
使用时引入:import PubSub from 'pubsub-js'
发布数据:
PubSub.publish('jjj',{kk:55s})//两个参数:第一个参数为传key,第二个是传value
接收数据:
let token = PubSub.subscribe('jjj', (_, data) => {
//两个参数:第一个参数是key,第二个是回调:回调中还有两个参数:1为key,2为value
console.log(data);
});
不使用时:
PubSub.unsubscribe(this.token)
3.9、使用fetch请求数据
fetch('')
.then((response) => {
console.log(response);
return response.json();
})
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
search = async () => {
try {
const response = fetch('');
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error);
}
};
四、React路由
4.1、路由模式
4.1.1、history
创建一个history:
let history = createBrowserHistory()
监听history:
history.listen((location)=>{
console.log(location)
})
goBack()回退,goForward()前进
push():添加
replace():替换(不会生成记录)
4.1.2、hash(兼容性极佳)
创建一个hash:
let history = createHashHistory()
4.2、react-router-dom
安装:这里安装的时5版本
npm install react-router-dom@5 --save
4.2.1、Link
import {Link} from 'react-router-dom'
使用时:
<Link to=''></Link>
to:填写地址名,Link被编译时,会变成a标签
如若使用时需高亮效果请使用:NavLink
定义activeClassName类:使用时注意权限
通过this.props.chirdren获取标签体内容
<NavLink to=''></NavLink>
4.2.2、BrowserRouter和HashRouter
import {BrowserRouter,HashRouter} from 'react-router-dom'
<BrowserRouter>//告诉浏览器history模式
<App/>
</BrowserRouter>
<HashRouter>//告诉浏览器history模式
<App/>
</HashRouter>
4.2.3、Route
import {Route} from 'react-router-dom'
<Route path='' component=''/>
path:地址名
component:组件名
4.2.4、路由组件和一般组件的区别
1,写法不同: 一般组件:<Demo/> 路由组件:<Route path="/demo" component={Demo}/ 2,存放位置不同: 一般组件:components 路由组件::pages 3,接收到的props不同 一般组件:写组件标签时传递了什么,就能收到什么 路由组件:接收到三个固定的属性 history: go:f go(n) goBack:f goBack() goForward:f goForvard() push:f push(path,state) replace:f replace(path,state) location: pathname;"/about search:"" state: undefined match: params:{} path: "/about" url:"/about"
4.2.5、switch
提高路由效率:用法如下
import {Switch} from 'react-router-dom'
<switch>
<Route to='/home' component='home'></Route>
<Route to='/home' component='test'></Route>
</switch>
如上述代码所示,如没有switch,Route会一直匹配下去,结果是两个组件内容都显示
相反,外面包一层switch,则只匹配第一个。
4.2.6、多级目录样式丢失问题
解决方案:
1、index.html中引用的样式表前面去点
<link href='./css/bootstrap.css'/>
<link href='/css/bootstrap.css'/>
2、index.html中引用的样式表前面加代码
<link href='./css/bootstrap.css'/>
<link href='%PUBLIC_URL%/css/bootstrap.css'/>
3、将history设为hash模式
使用方法看4.1.2
4.2.7、模糊匹配
从前往后匹配
<Link to='/home/a/b/c'/>
<Route path='/home'/>
原则Route要的有即可,但是顺序不能变
路由嵌套时:就是采用模糊匹配
子组件的路径为:父组件的路径名+子组件的路径名
<Link to='/home'/>父组件
<Link to='/home/news'/>子组件
4.2.8、严格匹配
默认是模糊匹配
要想开启严格匹配
两张写法
<Route exact path='/home'/>
<Route exact={true} path='/home'/>
必须一摸一样
非特殊情况下不要随意开启,有些时候可能会影响二级路由的匹配
4.2.9、Redirect
自闭和标签,一般放在最后
使用如下:
import {Redirect} from 'react-router-dom'
<Redirect to=''/>
4.3、路由传值
4.3.1、params参数
路由链接(携带参数):
<Link to='/dome/test/18'></Link>
注册路由(声明接收):
<Route path='/dome/test/:age' component={Test}/>
接收参数:通过props.match.params接收
console.log(this.props.match.params)
4.3.2、search参数
形似ajax中query
路由链接(携带参数):
<Link to={`/dome/test/?age=${}&name=${}`}></Link>
注册路由(正常注册):无须接收
<Route path='/dome/test' component={Test}/>
接收参数:通过props.location.search接收:接受的数据需要自己加工(推荐一个库:querystring(无需安装,react已经安装))
import qs from 'querystring';
let result = qs.parse(this.props.location.search.slice(1));
console.log(this.props.location.search);
多组key=value用&链接的方式:urlencoded编码方式
4.3.3、state参数
优点:浏览器url中无参数
清楚浏览器历史记录会报错
采用es6中||语法即可解决:看接收参数
路由链接(携带参数):
<Link to={{ pathname: '/home/test', state: { age: xx.age } }}></Link>;
注册路由(正常注册):无须接收
<Route path='/dome/test' component={Test} />;
接收参数:通过props.location.state接收
const { age } = this.props.location.state || {};
console.log(this.props.location.state);
4.4、路由跳转模式
4.4.1、push
默认就是push,可以在浏览器中留下历史记录
可以通过goBack()实现后退
可以通过goForward()实现前进
4.4.2、replace
如果想开启,方法如下:
<Link replace to={{pathname:'/home/test',state:{age:xx.age}}}></Link>
<Link replace={true} to={{ pathname: '/home/test', state: { age: xx.age } }}></Link>;
开启后则不会留下历史记录
4.5、编程式路由跳转
4.5.1、push
import React, { Component } from 'react';
export default class Index extends Component {
pushjump = () => {
//1) 传递params参数
this.props.history.push(`/home/test/${name}/${age}`);
//2) 传递search参数
this.props.history.push(`/home/test/?name=${name}&age=${age}`);
//3) 传递state参数
this.props.history.push(`/home/test`, { name, age });
};
render() {
return (
<div>
<button onClick={this.pushjump()}></button>
</div>
);
}
}
4.5.2、replace
import React, { Component } from 'react';
export default class Index extends Component {
pushjump = () => {
//1) 传递params参数
this.props.history.push(`/home/test/${name}/${age}`);
//2) 传递search参数
this.props.history.push(`/home/test/?name=${name}&age=${age}`);
//3) 传递state参数
this.props.history.push(`/home/test`, { name, age });
};
render() {
return (
<div>
<button onClick={this.pushjump()}></button>
</div>
);
}
}
补充:常用的还有go()、goBack()、goForward()
4.6、withRouter
在一般组件中使用路由组件中的API时,需调用withRouter来实现,否则报错,方法如下
import { withRouter } from 'react-router-dom';
export default withRouter(
class Test extends React.Component {
render() {
return;
}
},
);
import { withRouter } from 'react-router-dom';
class Test extends React.Component {
render() {
return;
}
}
export default withRouter(Test);
4.7、BrowserRouter和HashRouter的区别
1.底层原理不一样
BrowserRouter使用H5的history API,不兼容IE9一下版本
HashRouter使用的时URL的Hash值
2.path表现形式不一样
BrowserRouter的路径中没有#
HashRouter的路径包含#
3.刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state存放在history之中
HashRouter刷新后会导致state的数据丢失!!!!!!!!!!!
4.注:HashRoute会解决一些路径产生的问题
五、redux学习
5.1、安装
npm install redux
yarn add redux
5.2、store文件
在src文件下创建一个文件夹,名为store
1)store.js:创建store实例
2)actions:专门用于创建action
3)reducer:
4)constant:创建常量文件
内容为:
import { createStore } from 'redux';
//引入为组件服务的reducer
import test from './test';
//导出store
export default createStore(test);
reducer内容为:
const initState = '根据需要写对应的数据';
export default function test(preState = initState, action) {
/*switch(){
case : return
default: return preState
}*/
return;
}
参数preState:之前的状态
参数action:动作状态
注:开始前要初始化状态
5.2.1、getState()
获取状态值:
注:只有一个组件时用这个api!!!!!!
store.getState()
5.2.2、subscribe()
检测redux状态更新
import React, { Component } from 'react';
export default class index extends Component {
componentDidMount() {
store.subscribe(() => {
this.setState({});
});
}
render() {
return <div></div>;
}
}
如果使用较多建议写在index.js之中:一劳永逸
DOM的Diff算法不会引起大面积更新
store.subscribe(() => {
ReactDOM.render(<App />, document.getElementById('root'));
});
如果使用了react-redux,则不需要检测redux状态更新,react-redux会帮你检测
5.2.3、dispatch()
通知redux更改状态
store.dispatch()
5.2.4、异步action
export const test = () => {
return () => {};
};
需借助:redux-thunk:安装
npm install redux-thunk
import thunk from 'redux-thunk'
还需借助:applyMiddleware
export default createStore(test, applyMiddleware(thunk));
异步action中一般都会调用同步action,异步action不是必须要用的
六、react-redux
1、所有的UI组件都应该包裹一个容器组件,他们是父子关系。
2、容器组件是真正和redux打交道的,里面可以随意的使用redux的api。 3、UI组件中不能使用任何Redux的API。
4、容器组件会传给UI组件:(1).redux中所保存的状态。(2).用于操作状态的方法。
5、备注:容器给UI传递:状态、操作状态的方法,均通过props传递。
6、建议:先看不懂的话,将整合版跟上面的一起看
6.1、安装react-redux
npm install react-redux
创建一个js文件作为容器组件:
1)引入UI组件、引入redux中的store
import testui from './'
引入store需通过上层组件传递
在App组件中先引入容器组件,通过props的方式传递store(仅仅限于组件不多的情况)
import store from './store';
import Test from '';
<Test store={store} />;
在组件过多的情况下需借助:Provider,场景:在最外层的index.js里面
import store from './';
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
引入store,引入Provider,用Provider包裹App,将store传给Provider,react-redux会自动将需要store的组件,将store传过去
2)链接组件:借助connnet组件
import { connet } from 'react-redux';
import testui from './';
export default connet(mapStateToProps, mapDispatchToProps)(testui);
mapStateToProps:
1)用于传递状态
2)返回的是一个对象
3)返回对象中的key就作为传递传递给ui组件props的key,value就作为传递传递给ui组件props的value
mapDispatchToProps:用于操作状态的方法
1)用于传递操作状态的方法
2)返回的是一个对象
3)返回对象中的key就作为传递传递给ui组件props的key,value就作为传递传递给ui组件props的value
从一般写法到简写:推荐最下面的简写
function mapStateToProps(state) {
return { count: state };
}
function mapDispatchToProps(dispatch) {
return {
jia: () => {
dispatch({});
},
jian: () => {
dispatch({});
},
};
}
const mapStateToProps = (state) => ({ count: state });
const mapDispatchToProps = (dispatch) => ({
jia: () => {
dispatch({});
},
jian: () => {
dispatch({});
},
});
export default connet((state) => ({ count: state }), {
jia: testaction,
jian: testaction,
/*jian:直接写action,react-redux会自动分发dispatch*/
})(testui);
总结:
(1)、明确两个概念
1)UI组件不能使用任何redux的api,只负责页面交互
2)容器组件负责和redux通信,将结果交给UI组件
(2)、如何创建容器组件:借助react-redux中connnet函数
connet(mapStateToProps,mapDispatchToProps)(UI组件)
mapStateToProps:映射状态,返回值是一个对象
mapDispatchToProps:映射操作状态的方法,返回值是一个对象
(3)、容器组件中的store是靠props传进去的而不是在容器组件中直接引入
6.2、整合版
容器组件+UI组件
import React, { Component } from 'react';
import { actions } from './';
import connet from 'react-redux';
class Test extends Component {
divonclick = () => {
this.props.jia, this.props.jian;
};
render() {
return <div onClick={this.divonclick}></div>;
}
}
export default connet((state) => ({ count: state }), {
jia: testaction,
jian: testaction,
/*jian:直接写action,react-redux会自动分发dispatch*/
})(Test);
总结:
1.容器组件跟UI组件整合为一个组件
2.无需自己给容器传store,借助Provider一劳永逸
3.无需自己检测redux状态改变,使用react-redux之后,react-redux会帮你检测
4.mapDispatchToProps也可以写成一个对象
5.跟redux交流
1)定义UI组件不暴露
2)定义容器组件暴露,
3)在UI组件中通过this.props调用
6.3、redux数据共享
6.3.1、分门别类
当有多个组件时,redux中文件过多,就需要分门别类
首先创建两个文件夹
1、actions:所有的action放里面
2、reducers:所有的reducer放里面
6.3.2、多个reducer合并
借助combineReducer合并多个reducer:在store中修改如下
import { createStore, applyMiddleware, combineReducer } from 'redux';
import match from './';
import test from './';
const allReducers = combineReducer({
count: test,
age: match,
});
export default createStore(test, applyMiddleware(thunk));
combineReducer()传输一个对象,
对象中有多组key:value
key是自己起名字
value是reducer容器组件的返回值,直接写容器组件即可
6.3.3、纯函数
1、一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)
2、必须遵守一些约束
1)不得改写参数数据
2)不会产生任何副作用,例如网络请求
3)不能调用Date.now()或者Math.random()等不纯的方法
3、redux的reducer函数必须是一个函数
6.3.4、redux开发者工具
安装:
npm install redux-devtools-extension
在store中做修改,引入redux-devtools-extension
import { composeWithDevTools } from 'redux-devtools-extension';
//无异步actions时
export default createStore(test, composeWithDevTools());
//有异步actions时
export default createStore(test, composeWithDevTools(applyMiddleware(thunk)));
七、打包
npm build
借助serve开启服务器:安装serve
npm install serve -g
运行:
serve build
八、react扩展
8.1、setState
1)setState(stateChange,[callback])-----对象式的setState
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数,它在状态更新完毕、界面也更新后(render调用后)才被调用
2)setState(updater,[callback])-----函数式的setState
1.updater为返回stateChange对象的函数
2.stateChange可以接收到state和props
3.callback是可选的回调函数,它在状态更新完毕、界面也更新后(render调用后)才被调用
this.setState((state, props) => {
return {};
});
this.setState((state, props) => ({}));
总结:
1.对象式setState是函数式setState的简写方式(语法糖)
2.使用原则
(1)如果新状态不依赖于原状态==》使用对象方式
(2)如果新状态依赖于原状态==》使用函数方式
(3)如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中获得
8.2、lazyLoad
借助lazy、Suspense
import react,{Component,lazy,Suspense} from 'react'
const =lazy(()=>{import('./')})//懒加载路由
<Suspense fallback={组件}>
//注册路由
<Route></Route>
</Suspense>
8.3、Hooks
8.3.1、State Hooks
function Dome() {
const [name, setName] = React.useState('Tom');
function changeContent() {
setName('Jack'); //第一种写法
setName((name) => {
return 'Jack';
}); //第二种写法
}
return <h2 onClick={changeContent}>{name}</h2>;
}
总结:
1.State Hook让函数组件也可以有state状态,并进行状态数据的读写操作,
2.语法:count [状态名称,改变状态的方法名称] = React.useState(初始化值)
3.useState()说明:
参数:第一次初始化指定的值在内部做缓存
返回值:包含两个两个元素的数组,第一个为当前状态值,第二个为更新状态的方法
4.setState()两种写法:
setState(newvalue):参数为非函数值,直接指定新的状态值
setState((oldvalue)=>{newvalue}):参数为函数,接收原来的状态值,返回新的状态值
8.3.2、Effect Hooks
React.useEffect(() => {
return () => {
//在组件卸载前执行
};
}, []);
//如果指定的是 [],回调函数只会在第一次render())后执行,如果指定值,就检测值是否更新,如果更新重新调用该方法
(1)Effect Hook 可以让你在足数组件中执行副作用操作(用于模拟类组件中的生命周期钓子) (2)React中的副作用操作: 发ajax请求数据获取 设置订阅(启动定时器) 手动更改真实DOM (3)语法和说明: useEffect(O => { //在此可以执行任何带副作用操作 return() => {//在组件卸载前执行 //在此次做一些收尾工作,比和清除定时器/取消订阅等 } }, [stateValue])//如果指定的是 [ ],回调函数只会在第一次render())后执行,如果指定值,就检测值是否更新,如果更新重新调用该方法
(4)可以把useEffect Hook 看依如下二个函数的组合 componetnidMount() componentDidUpdate() componedEWTIUnmount()
8.3.3、Ref Hooks
function Dome() {
const myRef = React.useRef();
return <h2 ref={myRef}>555</h2>;
}
总结:
(1) Ref Hook可以在函数组件中存储/查找组件内的标签或任音其它数据 (2),语法:const refcontainer = useRef() (3).作用:保存标签对象,功能与React.createRef()一样
8.4、Fragment
作用:去除根标签
两种方法
<Fragment>
<input />
</Fragment>
<>
<input />
</>
当需要遍历时,请使用第一种,Fragment可以传key,空标签不可以
8.5、Context
适用于,祖组件给子组件传递数据(在开发中一般不用Context,一般用于封装react插件)
第一种:类组件给类组件传数据
const MyContext = React.createContext();
const { Provider } = MyContext;
export default class A extends React.Component {
state = { name: 'Jack' };
render() {
const { name } = this.state;
return (
<div>
<Provider value={{ name }}>
<B />
</Provider>
</div>
);
}
}
class B extends React.Component {
render() {
return (
<div>
<C />
</div>
);
}
}
class C extends React.Component {
static contextType = MyContext; //声明
render() {
return <div>接收到的数据为:{this.context.name}</div>;
}
}
第二种:
类组件给函数组件传数据
const MyContext = React.createContext();
const { Provider, Consumer } = MyContext;
export default class A extends React.Component {
state = { name: 'Jack' };
render() {
const { name } = this.state;
return (
<div>
<Provider value={{ name }}>
<B />
</Provider>
</div>
);
}
}
class B extends React.Component {
render() {
return (
<div>
<C />
</div>
);
}
}
function C() {
return (
<div>
接收到的数据为:
<Consumer>
{(value) => {
return value.name;
}}
</Consumer>
</div>
);
}
8.6、PureComponent
一下两种写法都可以,推荐PureComponent
export default class A extends React.Component {
state = { name: 'Jack' };
shouldComponentUpdate(preProps, preState) {
return !this.state.name == preState.name;
}
render() {
return <div></div>;
}
}
export default class A extends React.PureComponent {
state = { name: 'Jack' };
render() {
return <div></div>;
}
}
PureComponent帮你书写shouldComponentUpdate这个生命周期函数,检测props和state是否更新
当检测未更新时,shouldComponentUpdate,return false
当检测更新时,shouldComponentUpdate,return true
提高效率
8.7、render props
组件内将组件传输过去:类似Vue中的插槽
组件A传递
<A
render={(name) => {
<B name={name} />;
}}
></A>;
组件B接收:
this.props.render(name)
总结:
vue中: 使用slot技术,也就是通过组件标签体传入结构
<A><B/></A>
React中: 使用children props:通过组件标签体传入结构 使用render props:通过组件标签属性传入结构,一般用render函教属性
8.8、ErrorBoundary
错误边界:捕获后代组件错误,渲染出备用页面
注:只能捕获后代组件生命周期产生的错误!!!
使用:getDerivedStateFromError配合componentDidCatch
import React, { Component } from 'react';
export default class index extends Component {
//生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError() {
return {
hasError: true,
};
}
//在render之前触发
//返回新的state
componentDidCatch(error, info) {
//统计页面的错误。发送话求发送到后台去
console.log(error, info);
}
render() {
return <div></div>;
}
}
8.9、组件间通信方式
组件间的关系: 父子组件 兄弟组件非嵌套组件 祖孙组件(跨级组件
通信方式:
1.props: (1).childr en props (2).render prOps 2消息订阅-发布: pubs-sub、event等等 3.集中式管理: redux、dva等等 4.conText: 生产者-消费者模式
搭配方式:
父子组件:props 兄弟组件;消息订阅-发布、集中式管理 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
九、router6
react官网:默认使用函数式组件
安装最新版本router
npm install react-router-dom
1、之前的switch删除,现在用Routes包裹,功能一样
2、之前的component换成了element
3、之前组件注册直接写组件名,现在换成了写组件标签
import { Routes, Route } from 'react-router-dom';
<Routes>
<Route path='/test' element={<Test />} />
</Routes>;
Route还可以传一个属性:caseSensitive是否区分大小写,默认是否
4、之前的重定向删除,现在用Navigate:用法看下面代码
import { Navigate } from 'react-router-dom';
<Routes>
<Route path='/test' element={<Test />} />
<Route path='/' element={<Navigate to='/test' />} />
</Routes>;
Navigate还可以传replace属性默认是push开启看下面代码
<Route path='/' element={<Navigate to='/test' replace={true} />} />;
注:当使用Navigate时,就会切换视图,修改路径
5、之前‘实现高亮效果加activeclassName,现在要求是一个函数
<NavLink
className={({ isactive }) => {
isactive ? '原来的类名+活跃时的类名' : '原来的类名';
}}
></NavLink>;
function isactive({ isactive }) {
return isactive ? '原来的类名+活跃时的类名' : '原来的类名';
}
<NavLink className={isactive}></NavLink>;
6、useRoutes
路由表创建:
export default function App() {
const element = useRoutes([
{
path: '/test',
element: <Test />,
},
{
path: '/',
element: <Navigate to='/test' />,
},
]);
return <div>{element}</div>;
}
项目开发时一般在src下创建一个文件夹名为routes,里面创建index.js文件,对路由表集中管理
7、Outlet
作用:指定路由组件呈现的位置:与Vue中的router-view相似
注:自闭和标签
import { Outlet } from 'react-router-dom';
<Outlet />;
8、路由嵌套
const element = useRoutes([
{
path: '/test',
element: <Test />,
children: [{}],
},
{
path: '/',
element: <Navigate to='/test' />,
},
]);
跟vue很像:加children属性
在子组件的路由可以省略父组件的路由地址
path = '/test'; //父组件
path = 'new'; //子组件
path = './new'; //子组件
path = '/test/new'; //子组件
三种写法推荐第一种(省事)
9、路由传参
(1)params
<Link to={`test/${属性名}/${属性名}`}></Link>;
在路由表中做修改将原来的地址修改为path:'test/:属性名/:属性名'。
接收借助:useParams接收数据
import { useParams } from 'react-router-dom';
function Test() {
const a = useParams();
console.log(a);
}
扩展:了解即可
useMatch():需要传完整的路径才能解析到数据
const a = useMatch('/jjj/kkk/kkk/lll...');
(2)search
<Link to={`test/${属性名}/${属性名}`}></Link>;
接收借助:useSearchParams接收数据
import { useSearchParams } from 'react-router-dom';
const [search, setsearch] = useSearchParams();
const 属性名 = search.get('属性名');
(了解)setsearch:主要用于更新接收的数据,执行过后,地址栏会被修改,数据也会被修改
useLocation
import { useLocation } from 'react-router-dom';
const x = useLocation();
console.log(x);
(3)state
<Link to='/test' state={{ 属性名: 属性值 }}></Link>;
接收借助:useLocation
import { useLocation } from 'react-router-dom';
const {
state: { state属性 },
} = useLocation(); //连续解构赋值
10、编程式路由跳转
借助:useNavigate
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
function test() {
navigate('路径', {
replace: false,
state: {
属性名: 属性值,
},
});
}
<div onClick={test}></div>;
如需要接受值
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
function test(接收值) {
navigate('路径', {
replace: false,
state: {
属性名: 属性值,
},
});
}
<div
onClick={() => {
test(传值);
}}
></div>;
注:一般组件使用路由组件api时不需要之前的withRouter,直接使用useNavigate
前进useNavigate(1)
后退useNavigate(-1)
11-15知道即可不常用!!!
11、uselnRouterContext
作用:如果组件在Router的上下文中呈现,则 useInRouterContext钩子返回 true,否则返回 falsea
import { uselnRouterContext } from 'react-router-dom';
console.log(uselnRouterContext());
12、useNavigationType()
1.作用:返回当前的导航类型(用户是如何来到当前页面的) 2.返回值 POP PUSH REPLACE 3.备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)
import { useNavigationType } from 'react-router-dom';
console.log(useNavigationType());
13、useOutlet()
1,作用:用来呈现当前组件中渲染的嵌套路由
2.示例代码:
const result = useOutlet();
console, log(result);
//如果嵌套路由没有挂载,则result为nu11
//如果嵌套路由己经挂载,则展示嵌套的路由对象
14、10.useResolvedPath()
1.作用:给定一个URL值,解析其中的:path、search、hash值
import { useResolvedPath } from 'react-router-dom';
console.log(useResolvedPath('/test/?id=001&name=tom#has'));
内容到这里就结束啦, 谢谢大家的观看,如有错误:请联系qq:3259797986