基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 加载 React. -->
<!-- 注意:部署时,将"development.js"替换为"production.min.js"。 -->
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
</head>
<body>
<div id="app"></div>
<script>
// 1.创建虚拟DOM
let message = 'hello world'
let oDiv = React.createElement('div', null, message);
// 2.将虚拟DOM转换成真实DOM
ReactDOM.render(oDiv, document.getElementById('app'), () => {
console.log('创建完成');
})
</script>
</body>
</html>注意点:
-
多次调用ReactDOM.render方法,后渲染的会覆盖先渲染的
// 1.创建虚拟DOM let message = 'hello world' let oDiv = React.createElement('div', null, message); let oBtn = React.createElement('button', {onClick: myFn}, '按钮'); let oRoot = React.createElement('div', null, oDiv, oBtn); // 2.将虚拟DOM转换成真实DOM ReactDOM.render(oDiv, document.getElementById('app'), () => { console.log('创建完成'); }) // 注意点:多次调用ReactDOM.render方法,后渲染的会覆盖先渲染的 ReactDOM.render(oBtn, document.getElementById('app'), () => { console.log('创建完成'); }) -
React.createElement可以传多个DOM
let message = 'hello world' let oDiv = React.createElement('div', null, message); let oBtn = React.createElement('button', {onClick: myFn}, '按钮'); let oRoot = React.createElement('div', null, oDiv, oBtn); function myFn(){ alert(111); message = '你好,中国' let oDiv = React.createElement('div', null, message); let oBtn = React.createElement('button', {onClick: myFn}, '按钮'); let oRoot = React.createElement('div', null, oDiv, oBtn); ReactDOM.render(oRoot, document.getElementById('app'), () => { console.log('创建完成'); }) }
函数组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/babel.min/react.js"></script>
<script src="js/babel.min/react-dom@16.js"></script>
<script src="js/babel.min/balbel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
let message = 'hello world!'
function Home(){
return (
<div>
<div>{message}</div>
<button onClick={myFn} >按钮</button>
</div>
)
}
ReactDOM.render(<Home/>, document.querySelector('#app'))
function myFn(){
message = '你好,世界!'
ReactDOM.render(<Home/>, document.querySelector('#app'))
}
</script>
</body>
</html>类组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/react.js"></script>
<script src="js/react-dom@16.js"></script>
<script src="js/balbel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
let message = 'hello world!'
class Home extends React.Component{
render(){
return (
<div>
<div>{message}</div>
<button onClick={myFn} >按钮</button>
</div>
)
}
}
ReactDOM.render(<Home/>, document.querySelector('#app'))
function myFn(){
message = '你好,世界!'
ReactDOM.render(<Home/>, document.querySelector('#app'))
}
</script>
</body>
</html>状态组件
-
有状态组件和无状态组件?
首先要明确的事,组件中的状态(state)指的其实就是数据
- 有状态组件指的就是有自己数据的组件(逻辑组件)
- 无状态组件指的就是没有自己数据的组件(展示组件)
-
如何定义自己的状态?
-
凡是继承于React.Component的组件,默认都会从父类继承过来一个state属性
这个state属性就是专门用来保存当前数据的
-
所以但凡是继承于React.Component的组件,都是有状态组件
-
所以但凡不是继承于React.Component的组件,都是无状态组件
-
所以类组件就是有状态组件
-
所以函数组件就是无状态组件
-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/react.js"></script>
<script src="js/react-dom@16.js"></script>
<script src="js/balbel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
let message = 'hello world'
class Home extends React.Component{
render(){
return (
<div>
<div>{this.state + ''}</div>
<div>{message}</div>
<button onClick={myFn}>按钮</button>
</div>
)
}
}
ReactDOM.render(<Home/>, document.querySelector('#app'))
function myFn(){
message = '你好 世界';
ReactDOM.render(<Home/>, document.querySelector('#app'))
}
</script>
</body>
</html>this和state注意点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/react.js"></script>
<script src="js/react-dom@16.js"></script>
<script src="js/balbel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
class Home extends React.Component {
constructor() {
super();
this.state = {
message: 'hello world'
}
}
render() {
return (
<div>
<div>{this.state.message}</div>
<button onClick={this.myFn}>按钮</button>
</div>
)
}
// React在调用监听方法的时候,会通过apply修改监听方法的this
// 所以在普通的方法中,我们拿到的this是undefined
// 所以我们无法在普通的方法中通过this拿到当前组件的state
myFn = () => {
// 如果要修改state中的数据,那么永远不要直接修改
// 如果要修改state中的数据,那么需要通过setState方法来修改
this.setState({
message: '你好 世界!'
})
}
}
ReactDOM.render(<Home/>, document.querySelector('#app'))
</script>
</body>
</html>注释
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/react.js"></script>
<script src="js/react-dom@16.js"></script>
<script src="js/balbel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
class Home extends React.Component{
render(){
/*
* 1.在JSX中是不能使用HTML的注释的,因为JSX会把它当作是一个元素来处理
* <!--注释内容-->
* 2.在JSX中是不能使用JS的单行注释和多行注释的,因为元素中是有内容的,所以JSX会把JS的单行注释和多行注释当做是元素的内容来处理
* 3.在JSX中不能使用单行注释和多行注释的原因是,默认情况下JSX会把注释的内容当做是元素的内容来处理
* 所以我们只需要告诉JSX,注释的内容不是元素的内容即可
* 所以我们只需要将注释的内容写到{}中,JSX就会把注释的内容当做是JS来处理
* */
return (
<div>
{
// 单行注释
/*
* 多行注释
* */
}
<p>标题</p>
</div>
)
}
}
ReactDOM.render(<Home/>, document.querySelector('#app'))
</script>
</body>
</html>属性绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/react.js"></script>
<script src="js/react-dom@16.js"></script>
<script src="js/balbel.min.js"></script>
<style>
.message{
font-size: 100px;
color: #f00;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
class Home extends React.Component{
constructor() {
super();
this.state = {
message: 'hello world'
}
}
render(){
return (
<div>
{
/*
* 1.如果想通过JSX绑定类名,那么不能直接通过class来绑定
* JSX本质是转换成JS代码,而在JS代码中class是一个关键字
* 所以不能直接使用class来绑定类名
* 2.所以以后但凡名称但凡是JS关键字的属性,都不能直接绑定
* */
}
<p className="message" title={this.state.message}>{this.state.message}</p>
{
/*
* 3.如果想通过JSX绑定样式,那么不能像过去一样编写,必须通过对象的形式来绑定
* 4.在绑定样式的时候,如果过去在原生中是通过-连接的,那么就必须转换成驼峰命名
* */
}
<p style={{fontSize: '100px', color: '#ccc'}}>{this.state.message}</p>
</div>
)
}
}
ReactDOM.render(<Home/>, document.querySelector('#app'))
</script>
</body>
</html>事件绑定
如何解决监听方法中this默认是undefined的问题
-
通过箭头函数
-
通过添加监听方法的时候,手动通过bind的方式来修改监听方法中的this
-
通过在构造函数中,手动通过bind的方式来修改监听方法中的this
-
手动绑定一个箭头函数,然后再通过箭头函数的函数体中手动调用监听方法
箭头函数中的this,就是当前的实例对象
监听方法并不是React调用的,而是我们在箭头函数中手动调用的
普通的方法,默认情况下谁调用就是谁
ps:企业开发中推荐最后一种
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/react.js"></script>
<script src="js/react-dom@16.js"></script>
<script src="js/balbel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
class Home extends React.Component{
constructor() {
super();
this.myClick = this.btnClick.bind(this)
}
render(){
return (
<div>
<button onClick={this.btnClick}>按钮</button>
<button onClick={this.btnClick.bind(this)}>按钮</button>
<button onClick={this.myClick}>按钮</button>
<button onClick={() => {this.btnClick('abc')}}>按钮</button>
</div>
)
}
// btnClick = () => {
// console.log(this)
// }
btnClick(value){
console.log(this)
console.log(value);
}
}
ReactDOM.render(<Home/>, document.querySelector('#app'))
</script>
</body>
</html>事件对象
在React中当监听方法被触发的时候,React也会传递一个事件对象给我们
但是React传递给我们的这个事件对象并不是原生的事件对象,而是React根据原生的事件对象自己合成的一个事件对象
ps:虽然传递给我们的是React自己合成的事件对象,但是根据提供的API和元素的几乎一致,如果用到了一个没有提供的API,那么可以根据合成的事件对象拿到原生的事件对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/react.js"></script>
<script src="js/react-dom@16.js"></script>
<script src="js/balbel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
class Home extends React.Component{
constructor() {
super();
}
render(){
return (
<div>
<button onClick={(e) => {console.log(e)}}>按钮</button>
<button onClick={(e) => {console.log(e.nativeEvent)}}>按钮</button>
</div>
)
}
}
ReactDOM.render(<Home/>, document.querySelector('#app'))
</script>
</body>
</html>脚手架创建
安装
npm instal create-react-app -gcd 项目名称npm startnpm run eject显示项目webpack配置,这个操作不可逆
组件
// App.js
import React from "react";
import Header from "./components/Header";
class App extends React.Component {
render() {
return (
<div>
<Header/>
</div>
)
}
}
export default App;// Header.js
import React from "react";
import './Header.css';
function Header() {
return (
<div className={'header'}>头部2</div>
)
}
export default Header;// Header.css
.header{
background: pink;
}父子组件-父传子(函数)
// App.js
import React from "react";
import Header from "./components/Header";
class App extends React.Component {
render() {
return (
<div>
<Header msg={'hello world'}/>
</div>
)
}
}
export default App;
// Header.js
import React from "react";
import './Header.css';
function Header(props) {
console.log(props, 'props');
return (
<div className={'header'}>头部2</div>
)
}
export default Header;父子组件-子组件默认值
// App.js
import React from "react";
import Header from "./components/Header";
class App extends React.Component {
render() {
return (
<div>
<Header/>
</div>
)
}
}
export default App;// Header.js
import React from "react";
import './Header.css';
function Header({msg = '你好 世界!'}) {
// console.log(props, 'props');
console.log(msg);
return (
<div className={'header'}>头部2</div>
)
}
// 未来被废弃
// Header.defaultProps = {
// msg: '你好世界!'
// }
export default Header;父子组件-父传子(类)
// App.js
import React from "react";
import "./App.css"
import Main from "./components/Main";
class App extends React.Component {
render() {
return (
<div>
{/*<Main msg={'hello world!'}/>*/}
<Main/>
</div>
)
}
}
export default App;// Main.js
import React from 'react';
import './Main.css'
class Main extends React.Component {
constructor(props) {
super(props);
console.log(props);
}
render() {
console.log(this.props, 'this');
return (
<div className={'main'}>内容组件</div>
)
}
static defaultProps = {
msg: '你好 世界'
}
}
export default Main父子组件-子传父
// App.js
import React from "react";
import "./App.css"
import Footer from "./components/Footer";
class App extends React.Component {
render() {
return (
<div>
<Footer fatherFn={this.myFn.bind(this)}/>
</div>
)
}
myFn(msg){
console.log(msg);
}
}
export default App;// Footer.js
import React from 'react';
import './Footer.css'
class Footer extends React.Component {
constructor(props) {
super(props);
console.log(props, 'props');
}
render () {
return (
<div className={'footer'}>
底部2
<button onClick={() => {this.btnFn()}}>按钮</button>
</div>
)
}
btnFn(){
console.log(this, 'this');
this.props.fatherFn('hello world!')
}
}
export default Footer跨组件通讯-爷子
// App.js
import React from "react";
import "./App.css"
class Son extends React.Component{
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>{this.props.msg}</p>
<button onClick={() => this.btnClick()}>儿子按钮</button>
</div>
)
}
btnClick(){
this.props.appFn('你好世界!')
}
}
class Father extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>我是爸爸</p>
<Son msg={this.props.msg} appFn={this.props.appFn}></Son>
</div>
)
}
}
class App extends React.Component {
render() {
return (
<div>
<p>我是爷爷</p>
<Father msg={'hello world!'} appFn={this.myFn.bind(this)}></Father>
</div>
)
}
myFn(msg){
console.log(msg);
}
}
export default App;跨组件通讯-兄弟
// App.js
import React from "react";
import "./App.css"
class A extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<button onClick={() => {this.btnClick()}}>A组件按钮</button>
</div>
)
}
btnClick(){
this.props.appFn('hello world!')
}
}
class B extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<p>{this.props.msg}</p>
</div>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: ''
}
}
render() {
console.log(this, 'this');
return (
<div>
<A appFn={this.myFn.bind(this)}></A>
<B msg={this.state.msg}></B>
</div>
)
}
myFn(msg){
this.setState({
msg
})
}
}
export default App;props和state区别
props和state都是用来存储数据的
- props存储的是父组件传递过来的数据
- state存储的是自己的数据
- props是只读的
- state可读可写
// App.js
import React from "react";
import "./App.css"
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'zxy'
}
}
render() {
return (
<div>
<p>{this.props.msg}</p>
<p>{this.state.name}</p>
<button onClick={() => this.btnClick()}>按钮</button>
</div>
)
}
btnClick(){
this.setState({
name: 'yxj'
})
}
}
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Home msg={'hello world!'}></Home>
</div>
)
}
}
export default App;setState()
-
setState是同步的还是异步的默认情况下
setState是异步的 -
为什么
setState是异步的主要是为了性能优化
-
如何拿到更新之后的数据
setState方法其实可以接收两个参数通过
setState方法的第二个参数,通过回调函数拿到 -
setState一定是异步的吗不一定:在定时器和原生事件中
// App.js
import React from "react";
import "./App.css"
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0
}
}
render() {
return (
<div>
<p>{this.state.num}</p>
<button onClick={() => this.myFn()}>按钮</button>
</div>
)
}
myFn(){
this.setState({num: 10})
console.log(this.state.num, 'num'); // 0
this.setState({num: 10}, () => {
console.log(this.state.num, 'num2'); // 10
})
// 最终输出结果:0
// this.setState({
// num: this.state.num + 1
// })
// console.log(this.state.num);
// this.setState({
// num: this.state.num + 1
// })
// this.setState({
// num: this.state.num + 1
// })
// 最终输出结果:3
// this.setState((preValue, props) => {
// return {num: preValue.num + 1}
// })
// this.setState((preValue, props) => {
// return {num: preValue.num + 1}
// })
// this.setState((preValue, props) => {
// return {num: preValue.num + 1}
// })
}
}
export default App;生命周期
-
什么是生命周期
事物从生到死的过程中,我们称之为生命周期
-
什么事生命周期方法
事物在从生到死过程中,在特定时间节点调用的方法,我们称之为生命周期方法
-
React组件生命方法?
组件从生到死的过程,在特定的时间节点调用的方法,我们称之为组件的生命周期方法
- constructor函数:组件被创建的时候
- render函数:渲染组件的时候,就会调用
- componentDidMount函数:组件已经被挂在到DOM上时,就会回调
- componentDidUpdate函数:组件已经发生更新时,就会回调
- componentWillUnmount函数:组件即将被移除时,就会回调
import React from "react";
import "./App.css"
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0
}
console.log('挂载时-创建组件constructor');
/*
* 1.constructor生命周期方法中做什么?
* - 通过 props接收父组件传递过来的数据
* - 通过 this.state 初始化内部数据
* - 通过bind为事件绑定实例(this)
* - this.myClick = this.btnClick.bind(this)
* */
}
render() {
if(this.state.num === 0){
console.log('挂载时-渲染组件render');
}else {
console.log('更新时-渲染组件render');
}
/*
* 2.render生命周期方法中做什么
* - 返回组件的网页结构
* */
return(
<div>
<p>Home</p>
<p>{this.state.num}</p>
<button onClick={() => this.homeClick()}>更新</button>
</div>
)
}
componentDidMount() {
console.log('挂载时-渲染完成componentDidMount');
/*
* 3.componentDidMount生命周期方法中做什么?
* - 依赖于DOM操作可以在这里进行
* - 在此处发送网络请求最好的地方(官方建议)
* - 可以在此添加一些订阅(会在componentWillUnmount取消订阅)
* */
}
componentDidUpdate() {
console.log('更新时-更新完成componentDidUpdate');
/*
* 4.componentDidUpdate生命周期方法中做什么
* - 可以在此对更新之后的组件进行操作
* */
}
componentWillUnmount() {
console.log('卸载时-即将被卸载componentWillUnmount');
/*
* 5.componentWillUnmount生命周期方法中做什么
* - 在此方法中执行必要的清理操作
* - 例如,清除 timer,取消网络请求或清除
* - 在 componentWillUnmount()中创建的订阅等
* */
}
homeClick(){
this.setState({
num: this.state.num + 1
})
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isShow: true
}
}
render() {
return (
<div>
{this.state.isShow && <Home></Home>}
<button onClick={() => this.appClick()}>切换</button>
</div>
)
}
appClick(){
this.setState({
isShow: !this.state.isShow
})
}
}
export default App;##列表渲染
import React from "react";
import "./App.css"
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
nameList: ['张三', '李四', '王五']
}
}
render() {
return (
<div>
{
this.state.nameList.map(name => {
return (<li key={name}>{name}</li>)
})
}
</div>
)
}
}
export default App;组件性能优化(函数)
import React from "react";
import "./App.css"
// 性能优化:React.memo 解决父组件数据更新,子组件数据未变更也会跟着更新问题
const PurHome = React.memo(function () {
console.log('home调用了');
return (
<div>
<p>Home</p>
</div>
)
})
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0
}
}
render() {
console.log('app调用了');
return (
<div>
<PurHome></PurHome>
<p>{this.state.num}</p>
<button onClick={() => {this.appClick()}}>app按钮</button>
</div>
)
}
appClick() {
this.setState({num: this.state.num + 1})
}
}
export default App;组件性能优化(类)
import React from "react";
import "./App.css"
// 性能优化:React.PureComponent 解决父组件数据更新,子组件数据未变更也会跟着更新问题
class Home extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
num: 0
}
}
render() {
console.log('home更新了');
return (
<div>
<p>{this.state.num}</p>
<button onClick={() => {this.homeClick()}}>home按钮</button>
</div>
)
}
homeClick() {
this.setState({num: this.state.num + 1})
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0
}
}
render() {
console.log('app更新了');
return (
<div>
<Home></Home>
<p>{this.state.num}</p>
<button onClick={() => {this.appClick()}}>app按钮</button>
</div>
)
}
appClick() {
this.setState({num: this.state.num + 1})
}
}
export default App;Ref
// App.js
import React from "react";
import "./App.css"
class App extends React.Component {
constructor(props) {
super(props);
// 方式一
// this.opRef = React.createRef();
// 方式二
this.opRef = null
}
render() {
return (
<div>
{/*方式一*/}
{/*<p ref={this.opRef}>App</p>*/}
{/*方式二*/}
<p ref={(arg) => {this.opRef = arg}}>App</p>
<button onClick={() => {this.appClick()}}>按钮</button>
</div>
)
}
appClick(){
// 方式一
// console.log(this.opRef.current, '---');
// 方式二
console.log(this.opRef, '---');
}
}
export default App;注意点:
- 如果获取的是原生的元素,那么拿到的就是元素本身
- 如果获取的是类组件元素,那么拿到的类组件的实例对象
- 如果获取的是函数组件元素,那么什么都拿不到
Ref转发
虽然无法直接获取函数组件元素,但是可以通过React.forwardRef转发获取函数组件元素中的内容
// App.js
import React from "react";
import "./App.css"
const Home = React.forwardRef(function(props, myRef){
return (
<div ref={myRef}>
<p>我是段落</p>
<span>我是span</span>
</div>
)
})
class App extends React.PureComponent {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return (
<div>
<Home ref={this.myRef}></Home>
<button onClick={() => {this.btnClick()}}>按钮</button>
</div>
)
}
btnClick(){
console.log(this.myRef.current);
}
}
export default App;受控组件-input
// app.js
import React from "react";
import "./App.css"
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
name: '张三',
email: '19851090728@163.com',
phone: 19851090728
}
}
render() {
return (
<div>
<form>
<input type="text" name="name" value={this.state.name} onChange={(e) => this.change(e)}/>
<input type="text" name="email" value={this.state.email} onChange={(e) => this.change(e)}/>
<input type="number" name="phone" value={this.state.phone} onChange={(e) => this.change(e)}/>
</form>
</div>
)
}
change(e) {
this.setState({
[e.target.name]: e.target.value
})
}
}
export default App;非受控组件
// App.js
import React from "react";
import "./App.css"
class App extends React.PureComponent {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return (
<div>
<form onSubmit={(e) => this.submit(e)}>
<input type="text" ref={this.myRef}/>
<input type="submit"/>
</form>
</div>
)
}
submit(e){
e.preventDefault();
console.log(this.myRef.current.value, '---');
}
}
export default App;##高阶组件
// App.js
import React from "react";
import "./App.css"
class Home extends React.Component {
render() {
return (
<div>Home</div>
)
}
}
function enhanceComponent(WrapperComponent) {
return class AddComponent extends React.Component {
render() {
return (
<div>
<WrapperComponent></WrapperComponent>
</div>
)
}
}
}
const AdvComponent = enhanceComponent(Home);
class App extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<div>
<AdvComponent></AdvComponent>
</div>
)
}
}
export default App;高阶组件-应用场景1(代码复用)
// App.js
import React from "react";
import "./App.css"
const {Provider, Consumer} = React.createContext({});
function EnhancedComponent(WrappedComponent) {
return class father extends React.PureComponent {
render() {
return (
<Consumer>
{value => {
return (
// <WrappedComponent msg={value.msg} num={value.num}></WrappedComponent>
// 等同于上面写法
<WrappedComponent {...value}></WrappedComponent>
)
}}
</Consumer>
)
}
}
}
class son1 extends React.PureComponent {
render() {
return(
<div>
<p>{this.props.msg}</p>
<p>{this.props.num}</p>
</div>
)
}
}
class son2 extends React.PureComponent {
render() {
return(
<ul>
<li>{this.props.msg}</li>
<li>{this.props.num}</li>
</ul>
)
}
}
const Father1 = EnhancedComponent(son1)
const Father2 = EnhancedComponent(son2)
class App extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<Provider value={{msg: ' hello world!', num: 20}}>
<Father1></Father1>
<Father2></Father2>
</Provider>
)
}
}
export default App;高阶组件-应用场景2(增强props)
// App.js
import React from "react";
import "./App.css"
const {Provider, Consumer} = React.createContext({});
function EnhancedComponent(WrappedComponent) {
return class father extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<Consumer>
{value => {
return (
<WrappedComponent {...value} {...this.props}></WrappedComponent>
)
}}
</Consumer>
)
}
}
}
class son1 extends React.PureComponent {
render() {
return(
<div>
<p>{this.props.msg}</p>
<p>{this.props.num}</p>
<p>{this.props.from}</p>
</div>
)
}
}
class son2 extends React.PureComponent {
render() {
return(
<ul>
<li>{this.props.msg}</li>
<li>{this.props.num}</li>
<li>{this.props.from}</li>
</ul>
)
}
}
const Father1 = EnhancedComponent(son1)
const Father2 = EnhancedComponent(son2)
class App extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<Provider value={{msg: ' hello world!', num: 20}}>
<Father1 from="china"></Father1>
<Father2 from="america"></Father2>
</Provider>
)
}
}
export default App;高阶组件-应用场景3(生命周期拦截)
// App.js
import React from "react";
import "./App.css"
import {EventEmitter} from "events";
const {Provider, Consumer} = React.createContext({});
const eventBus = new EventEmitter();
class son1 extends React.PureComponent {
render() {
return(
<div>
<p>{this.props.msg}</p>
<p>{this.props.num}</p>
<p>{this.props.from}</p>
{
this.props.list.map(value => {
return (
<p key={value}>{value}</p>
)
})
}
</div>
)
}
}
class son2 extends React.PureComponent {
render() {
return(
<ul>
<li>{this.props.msg}</li>
<li>{this.props.num}</li>
<li>{this.props.from}</li>
{
this.props.list.map(value => {
return (
<li key={value}>{value}</li>
)
})
}
</ul>
)
}
}
function EnhancedComponent(WrappedComponent) {
return class father extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
list: []
}
}
componentDidMount() {
eventBus.addListener('update', this.update.bind(this))
}
componentWillUnmount() {
eventBus.removeListener('update', this.update.bind(this))
}
update(list){
this.setState({
list: list
})
}
render() {
return (
<Consumer>
{value => {
return (
<WrappedComponent {...value} {...this.props} {...this.state}></WrappedComponent>
)
}}
</Consumer>
)
}
}
}
const Father1 = EnhancedComponent(son1)
const Father2 = EnhancedComponent(son2)
class App extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<Provider value={{msg: ' hello world!', num: 20}}>
<Father1 from="china"></Father1>
<Father2 from="america"></Father2>
<button onClick={() => {this.myClick()}}>按钮</button>
</Provider>
)
}
myClick(){
console.log(111);
eventBus.emit('update', ['张三', '李四', '王五'])
}
}
export default App;高阶组件-应用场景4(权限控制)
// App.js
import React from "react";
import "./App.css"
class Info extends React.PureComponent {
render() {
return (
<div>用户信息</div>
)
}
}
class Login extends React.PureComponent{
render(){
return (
<div>登陆页面</div>
)
}
}
function EnhancedComponent(WrappedComponent) {
return class AuthorityComponent extends React.PureComponent {
render() {
if(this.props.isLogin){
return (<Info></Info>)
}else {
return (<Login></Login>)
}
}
}
}
const AuthorityInfo = EnhancedComponent(Info);
class App extends React.PureComponent {
render() {
return(
<AuthorityInfo isLogin={false}></AuthorityInfo>
)
}
}
export default App;Portals
- 默认情况下,所有的组件都是渲染到
root元素中的 Portal提供了一种将组件渲染到其它元素中的能力
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>React App</title>
</head>
<body>
<div id="root"></div>
<div id="other"></div>
</body>
</html>// App.js
import React from "react";
import "./App.css"
import ReactDom from "react-dom";
class Modal extends React.PureComponent {
render() {
return ReactDom.createPortal(this.props.children, document.getElementById("other"));
}
}
class App extends React.PureComponent {
render() {
return(
<Modal>
<div id="modal">123</div>
</Modal>
)
}
}
export default App;Fragment
Fragment标签就相当于简单的标签,在react项目中就充当组件根节点的div使用,是要使用Fragment标签,那么在react中渲染的时候,就会被自动忽略掉,不会被渲染
// App.js
import React, {Fragment} from "react";
import "./App.css"
class Home extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
list: ['zs', 'ls', 'ww']
}
}
render() {
/*
* Fragment标签可以添加key
* 语法糖:<></>
* ps:
* - 语法糖不能添加key
* */
return (
(
<Fragment>
{
this.state.list.map(name => {
return (<p key={name}>{name}</p>)
})
}
</Fragment>
)
)
}
}
class App extends React.PureComponent {
render() {
return(
<>
<Home></Home>
</>
)
}
}
export default App;内联样式
React并没有像Vue那样提供特定的区域给我们编写CSS代码
内联样式的优点:
- 内联样式,样式之间不会有冲突
- 可以动态获取当前state中的状态
内联样式的缺点:
- 写法上需要使用驼峰标识
- 某些样式没有提示
- 大量的样式,代码混乱
- 某些样式无法编写(比如伪类/伪元素)
import React, {Fragment} from "react";
import "./App.css"
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
color: 'yellow'
}
}
render() {
return(
<>
<p style={{fontSize: '50px', color: this.state.color}}>我是段落1</p>
<p style={{fontSize: '50px', color: 'green'}}>我是段落2</p>
</>
)
}
}
export default App;外链样式
将CSS代码写到一个单独的CSS文件中,在使用的时候导入进来
外链样式的优点:
- 编写简单,有代码提示,支持所有
CSS代码
外链样式的缺点:
- 不可以动态获取当前
state中的状态 - 属于全局
CSS,样式之间会相互影响
// Home.js
import React from "react";
import './Home.css'
class Home extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<>
<p>我是段落1</p>
</>
)
}
}
export default Home/*Home.css*/
p{
font-size: 50px;
color: red;
}// Head.js
import React from "react";
import './Head.css'
class Head extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<>
<p>我是段落1</p>
</>
)
}
}
export default Head/*Head.css*/
p{
font-size: 50px;
color: blue;
}
CSS模块化
React的脚手架已经内置了css module的配置:
.css/.less/.scss等样式文件都修改成.module.css/.module.less/.module.scss
Css Module优点:
- 编写简单,有代码提示,支持所有
CSS语法 - 解决了全局样式相互污染问题
Css Module缺点:
- 不可以动态获取当前
state中的状态
// Home.js
import React from "react";
import styles from './Home.module.css'
class Home extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<>
<p className={styles.text}>Home段落</p>
</>
)
}
}
export default Home// Home.module.css
.text{
font-size: 50px;
color: #f0f;
}// Head.js
import React from "react";
import styles from "./Head.module.css";
class Head extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<>
<p className={styles.text}>Head段落</p>
</>
)
}
}
export default Head// Head.module.css
.text{
font-size: 50px;
color: blue;
}CSS-in-JS
在React中,React认为结构和逻辑是密不可分的,所以在React中结构代码也是通过JS来编写的
正是收到React这种思想的影响,所以就有很多人开发了用JS来编写CSS库
styled-components /emotion
利用JS来编写CSS,可以让CSS具备样式嵌套、函数定义、逻辑复用、动态修改状态等特性
- 也就是说,从某种层面上,提供了比过去
Less/Scss更为强大的功能 - 所以
Css-in-JS在React中也是一种比较推荐的方式
import React from "react";
import styled from "styled-components";
const StyleDiv = styled.div`
color: #f00;
a{
font-size: 50px;
}
`
class Home extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<StyleDiv>
<p>Home段落</p>
<a href="www.it666.com">我是home超链接</a>
</StyleDiv>
)
}
}
export default Homestyled-components
props /attrs
import React from "react";
import styled from "styled-components";
// props传参
const StyleDiv = styled.div`
color: ${props => props.color};
a{
font-size: 50px;
color: ${props => props.color};
}
`
// attrs修改
const StyleInput = styled.input.attrs({
type: "password"
})``
class Home extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
color: 'red'
}
}
render() {
return (
<StyleDiv color={this.state.color}>
<p>Home段落</p>
<a href="www.it666.com">我是home超链接</a>
<button onClick={() => this.myClick()}>按钮</button>
<StyleInput></StyleInput>
</StyleDiv>
)
}
myClick(){
this.setState({
color: 'green'
})
}
}
export default Home修改主题
// App.js
import React, {Fragment} from "react";
import Home from './components/Home'
import Head from './components/Head'
import {ThemeProvider} from 'styled-components'
class App extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return(
<ThemeProvider theme={{fontSize: '50px', color: 'pink'}}>
<Home></Home>
<Head></Head>
</ThemeProvider>
)
}
}
export default App;// Home.js
import React from "react";
import styled from "styled-components";
const StyleDiv = styled.div`
color: ${props => props.theme.color};
font-size: ${props => props.theme.fontSize};
a{
color: ${props => props.theme.color};
font-size: ${props => props.theme.fontSize};
}
`
class Home extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<StyleDiv>
<p>Home段落</p>
</StyleDiv>
)
}
}
export default Home// Head.js
import React from "react";
import styled from "styled-components";
const StyleDiv = styled.div`
p{
color: ${props => props.theme.color};
font-size: ${props => props.theme.fontSize};
}
`
class Head extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<StyleDiv>
<p>Head段落</p>
</StyleDiv>
)
}
}
export default Head继承
import React, {Fragment} from "react";
import styled from "styled-components"
const StyledP = styled.p`
background: skyblue;
font-size: 50px;
`
const StyleP1 = styled(StyledP)`
color: #f00;
`
const StyleP2 = styled(StyledP)`
color: #f0f;
`
class App extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return(
<>
<StyleP1>我是段落1</StyleP1>
<StyleP2>我是段落2</StyleP2>
</>
)
}
}
export default App;动画
在React中我们可以通过原生的CSS来实现过渡动画
但是React社区为我们提供了react-transition-group帮助我们快速过渡动画
-
Transition容器组件该组件是一个和平台无关的组件(不一定要结合CSS)
在前端开发中,我们一般是结合
CSS来完成过渡,所以比较常用的是CSSTransition -
CSSTransition在前端开发中,通常使用
CSSTransition来完成过渡动画效果 -
SwitchTransition容器组件两个组件显示和隐藏切换时,使用该组件
-
TransitionGroup容器组件将多个动画组件包裹在其中,一般用于列表中元素的动画
原生
import React, {Fragment} from "react";
import styled from "styled-components"
const StyleDiv = styled.div`
width: ${props => props.width};
height: ${props => props.height};
background: skyblue;
opacity: ${props => props.opacity};
transition: all 3s;
`
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
width: 0,
height: 0,
opacity: 0
}
}
render() {
return(
<>
<StyleDiv {...this.state}></StyleDiv>
<button onClick={() => this.myFn()}>按钮</button>
</>
)
}
myFn(){
this.setState({
width: '100px',
height: '100px',
opacity: 1
})
}
}
export default App;CSSTransition
-
安装
react-transition-groupnpm install react-transition-group --save -
从安装好的库中导入
CSSTransitionimport {CSSTransition} from 'react-transition-group' -
利用
CSSTransition将需要执行过渡效果的组件或元素包裹起来 -
编写对应的
CSS动画实现:
.-enter / .-enter-active / .-enter-done / .-exit / .-exit-active / .-exit-done / .-appear / .-appear-active / .-appear-done -
CSSTransition的一些属性in: 取值是一个布尔值,如果取值为
false表示触发退出动画,如果取值是true表示触发进入动画classNames: 指定动画类名的前缀
timeout: 设置动画超时时间
unmountOnExit: 如果取值为
true,那么表示退出动画执行完毕以后删除对应的元素appear: 元素初始化时执行动画
// App.js
import React, {Fragment} from "react";
import {CSSTransition} from "react-transition-group";
import './App.css'
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isShow: true
}
}
render() {
return(
<>
<CSSTransition in={this.state.isShow} classNames={"box"} timeout={2000} unmountOnExit={true} appear>
<div className={'box'}></div>
</CSSTransition>
<button onClick={() => this.show()}>显示</button>
<button onClick={() => this.hide()}>隐藏</button>
</>
)
}
show(){
this.setState({isShow: true})
}
hide(){
this.setState({isShow: false})
}
}
export default App;/* App.css */
/*动画执行前*/
.box-enter{
width: 0;
height: 0;
background: red;
opacity: 0;
}
/*动画执行过程中*/
.box-enter-active{
width: 100px;
height: 100px;
opacity: 1;
transition: all 2s;
}
/*动画执行完毕后*/
.box-enter-done{
width: 100px;
height: 100px;
background: skyblue;
opacity: 1;
}
.box-exit{
width: 100px;
height: 100px;
opacity: 1;
background: skyblue;
}
.box-exit-active{
width: 0;
height: 0;
opacity: 0;
transition: all 2s;
}
.box-exit-done{
width: 0;
height: 0;
background: red;
opacity: 0;
}
.box-appear{
width: 0;
height: 0;
background: red;
opacity: 0;
}
.box-appear-active{
width: 100px;
height: 100px;
opacity: 1;
transition: all 2s;
}
.box-appear-done{
width: 100px;
height: 100px;
background: skyblue;
opacity: 1;
}- 回调函数
// App.js
import React, {Fragment} from "react";
import {CSSTransition} from "react-transition-group";
import './App.css'
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isShow: true
}
}
render() {
return(
<>
<CSSTransition in={this.state.isShow}
classNames={"box"}
timeout={2000}
unmountOnExit={true}
appear
onEnter={this.enter}
onEntering={() => {this.enterIng()}}
onEntered={() => {this.enterEd()}}>
<div className={'box'}></div>
</CSSTransition>
<button onClick={() => this.show()}>显示</button>
<button onClick={() => this.hide()}>隐藏</button>
</>
)
}
show(){
this.setState({isShow: true})
}
hide(){
this.setState({isShow: false})
}
enter(currentEl, isAppear){
console.log(currentEl, isAppear);
}
enterIng(){
console.log('进入动画执行过程中');
}
enterEd(){
console.log('动画执行完毕');
}
}
export default App;/*App.css*/
/*动画执行前*/
.box-enter{
width: 0;
height: 0;
background: red;
opacity: 0;
}
/*动画执行过程中*/
.box-enter-active{
width: 100px;
height: 100px;
opacity: 1;
transition: all 2s;
}
/*动画执行完毕后*/
.box-enter-done{
width: 100px;
height: 100px;
background: skyblue;
opacity: 1;
}
.box-exit{
width: 100px;
height: 100px;
opacity: 1;
background: skyblue;
}
.box-exit-active{
width: 0;
height: 0;
opacity: 0;
transition: all 2s;
}
.box-exit-done{
width: 0;
height: 0;
background: red;
opacity: 0;
}
.box-appear{
width: 0;
height: 0;
background: red;
opacity: 0;
}
.box-appear-active{
width: 100px;
height: 100px;
opacity: 1;
transition: all 2s;
}
.box-appear-done{
width: 100px;
height: 100px;
background: skyblue;
opacity: 1;
}SwitchTransition
SwitchTransition可以完成组件切换的动画
SwitchTransition组件里面要有CSSTransition或者Transition组件,不能直接包裹你想要的组件
SwitchTransition里面的CSSTransition或Transition组件不能像以前那样接收in属性来判断元素是何种状态,通过key属性
// app.js
import React, {Fragment} from "react";
import {CSSTransition, SwitchTransition} from "react-transition-group";
import './App.css'
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isOn: true
}
}
render() {
return(
<>
<SwitchTransition>
<CSSTransition key={this.state.isOn} timeout={3000} classNames={'btn'}>
<button onClick={() => this.btnClick()}>{this.state.isOn ? 'on' : 'off'}</button>
</CSSTransition>
</SwitchTransition>
</>
)
}
btnClick(){
this.setState({
isOn: !this.state.isOn
})
}
}
export default App;// app.css
.btn-enter{
opacity: 0;
transform: translateX(-100%);
}
/*动画执行过程中*/
.btn-enter-active{
opacity: 1;
transition: all 3s;
transform: translateX(0%);
}
/*动画执行完毕后*/
.btn-enter-done{
}
.btn-exit{
opacity: 1;
transform: translateX(0%);
}
.btn-exit-active{
opacity: 0;
transform: translateX(100%);
transition: all 3s;
}
.btn-exit-done{
}
button{
padding: 10px 20px;
margin-left: 50%;
}TransitionGroup
当我们有一组组件需要执行动画的时候,就需要将这些CSSTransition放入到一个TransitionGroup中来完成动画
注意点和SwitchTransition一样
// App.js
import React, {Fragment} from "react";
import {CSSTransition, TransitionGroup} from "react-transition-group";
import './App.css'
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
list: [
{
name: 'zs',
id: 0
},
{
name: 'ls',
id: 1
},
{
name: 'ww',
id: 2
},
]
}
}
render() {
return(
<>
<TransitionGroup>
{
this.state.list.map((item, index) => {
return (
<CSSTransition timeout={3000}
key={item.id}
classNames={'item'}>
<li key={item.id} onClick={() => this.removeItem(index)}>{item.name}</li>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick={() => {this.btnClick()}}>新增</button>
</>
)
}
removeItem(index) {
let newList = this.state.list.filter((item, subIndex) => {
return index !== subIndex
})
this.setState({
list: newList
})
}
btnClick(){
this.setState({
list: [...this.state.list, {id: this.state.list.length + 1, name: 'zxy'}]
})
}
}
export default App;// App.css
.item-enter{
opacity: 0;
transform: translateX(100%);
}
/*动画执行过程中*/
.item-enter-active{
opacity: 1;
transition: all 3s;
transform: translateX(0%);
}
/*动画执行完毕后*/
.item-enter-done{
}
.item-exit{
opacity: 1;
transform: translateX(0%);
}
.item-exit-active{
opacity: 0;
transform: translateX(100%);
transition: all 3s;
}
.item-exit-done{
}路由
路由维护了URL地址和组件的映射关系,通过这个映射关系
我们就可以根据不同的URL地址,去渲染不同的组件
基本使用
-
如何在
React中使用路由安装
react-routernpm install react-router-dom -
通过指定监听模式
BrowserRouter:history模式HashRouter:hash模式 -
通过
Link修改路由URL地址 -
通过
Route匹配路由地址
import React from "react";
import {BrowserRouter, HashRouter, Route, Link} from "react-router-dom";
import './App.css';
import Home from "./components/Home";
import About from "./components/About";
function App() {
return (
<div className="App">
{/*<BrowserRouter>*/}
{/* <Link to={'/home'}>home</Link>*/}
{/* <Link to={'/about'}>about</Link>*/}
{/* <Route path={'/home'} component={Home}></Route>*/}
{/* <Route path={'/about'} component={About}></Route>*/}
{/*</BrowserRouter>*/}
<HashRouter>
<Link to={'/home'}>home</Link>
<Link to={'/about'}>about</Link>
<Route path={'/home'} component={Home}></Route>
<Route path={'/about'} component={About}></Route>
</HashRouter>
</div>
);
}
export default App;注意点
-
Route注意点:Route在匹配路由的时候,是利用当前资源地址从左至右的和path中的地址进行匹配的只要当前资源地址从左至右完整的包含了
path中的地址那么就认为匹配当前资源地址:
/home/aboutpath中的地址:/homepath中的地址:/home/about在
Route上添加exact那么就可以解决 -
NavLink注意点:NavLink在匹配路由的时候,是利用当前资源地址从左至右的和path中的地址进行匹配的只要当前资源地址从左至右完整的包含了
path中的地址那么就认为匹配当前资源地址:
/home/aboutpath中的地址:/homepath中的地址:/home/about在
NavLink上添加exact那么就可以解决 -
Link和NavLink注意点:默认情况下
Link会渲染成一个a标签如果想渲染成其它元素,可以通过手动路由跳转来实现
// App.js
import React from "react";
import {BrowserRouter, HashRouter, Route, Link, NavLink} from "react-router-dom";
import './App.css';
import Home from "./components/Home";
import About from "./components/About";
function App() {
return (
<div className="App">
<BrowserRouter>
{/*<Link to={'/home'}>home</Link>*/}
{/*<Link to={'/home/about'}>about</Link>*/}
<NavLink exact to={'/home'} activeStyle={{color: 'red'}}>home</NavLink>
<NavLink exact to={'/home/about'} activeStyle={{color: 'red'}}>about</NavLink>
<Route exact path={'/home'} component={Home}></Route>
<Route exact path={'/home/about'} component={About}></Route>
</BrowserRouter>
</div>
);
}
export default App;Switch
siwtch让众多的Route一旦被匹配到,后面就不会再进行匹配
import React from "react";
import {BrowserRouter, HashRouter, Switch, Route, Link, NavLink} from "react-router-dom";
import './App.css';
import Home from "./components/Home";
import About from "./components/About";
import Other from "./components/Other";
function App() {
return (
<div className="App">
<BrowserRouter>
<NavLink exact to={'/home'} activeStyle={{color: 'red'}}>home</NavLink>
<NavLink exact to={'/home/about'} activeStyle={{color: 'red'}}>about</NavLink>
<Switch>
<Route exact path={'/home'} component={Home}></Route>
<Route exact path={'/home/about'} component={About}></Route>
{/*如果Route没有指定path,那么表示这个Route和所有资源都匹配*/}
<Route component={Other}></Route>
</Switch>
</BrowserRouter>
</div>
);
}
export default App;Redirect
资源重定向,也就是可以在访问某个资源地址的时候重定向到另一个资源地址
例如:访问/user 重定向到 /login
// App.js
import React from "react";
import {BrowserRouter, HashRouter, Switch, Route, Link, NavLink} from "react-router-dom";
import './App.css';
import Home from "./components/Home";
import About from "./components/About";
import Login from "./components/Login";
import User from "./components/User";
function App() {
return (
<div className="App">
<BrowserRouter>
<NavLink exact to={'/home'} activeStyle={{color: 'red'}}>home</NavLink>
<NavLink exact to={'/home/about'} activeStyle={{color: 'red'}}>about</NavLink>
<NavLink exact to={'/user'} activeStyle={{color: 'red'}}>user</NavLink>
<Switch>
<Route exact path={'/home'} component={Home}></Route>
<Route exact path={'/home/about'} component={About}></Route>
<Route exact path={'/login'} component={Login}></Route>
<Route exact path={'/user'} component={User}></Route>
</Switch>
</BrowserRouter>
</div>
);
}
export default App;import React from 'react';
import {Redirect} from 'react-router-dom';
class User extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isLogin: true
}
}
render() {
let User = (
<div>User</div>
)
let Login = <Redirect to={'/login'}></Redirect>
return this.state.isLogin ? User : Login;
}
}
export default User;嵌套路由
路由里面又有路由,我们称之为嵌套路由
注意点:
- 如果要使用嵌套路由,那么外层路由不能添加精准匹配
exact
// App.js
import React from "react";
import {BrowserRouter, HashRouter, Switch, Route, Link, NavLink} from "react-router-dom";
import './App.css';
import Home from "./components/Home";
import About from "./components/About";
import Login from "./components/Login";
import User from "./components/User";
import Discover from "./components/Discover";
function App() {
return (
<div className="App">
<BrowserRouter>
<NavLink exact to={'/home'} activeStyle={{color: 'red'}}>home</NavLink>
<NavLink exact to={'/home/about'} activeStyle={{color: 'red'}}>about</NavLink>
<NavLink exact to={'/user'} activeStyle={{color: 'red'}}>user</NavLink>
<NavLink exact to={'/discover'} activeStyle={{color: 'red'}}>广场</NavLink>
<Switch>
<Route exact path={'/home'} component={Home}></Route>
<Route exact path={'/home/about'} component={About}></Route>
<Route exact path={'/login'} component={Login}></Route>
<Route exact path={'/user'} component={User}></Route>
<Route path={'/discover'} component={Discover}></Route>
</Switch>
</BrowserRouter>
</div>
);
}
export default App;// Discover.js
import React from 'react';
import {NavLink, Switch, Route} from 'react-router-dom';
function Recommend() {
return (
<div>推荐</div>
)
}
function TopList() {
return (
<div>排行榜</div>
)
}
function PlayList() {
return (
<div>歌单</div>
)
}
class Discover extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isLogin: true
}
}
render() {
return (
<div>
<NavLink exact to={'/discover'} activeStyle={{color: 'red'}}>推荐</NavLink>
<NavLink exact to={'/discover/toplist'} activeStyle={{color: 'red'}}>排行榜</NavLink>
<NavLink exact to={'/discover/playlist'} activeStyle={{color: 'red'}}>歌单</NavLink>
<Switch>
<Route exact path={'/discover'} component={Recommend}></Route>
<Route exact path={'/discover/toplist'} component={TopList}></Route>
<Route exact path={'/discover/playlist'} component={PlayList}></Route>
</Switch>
</div>
)
}
}
export default Discover;手动路由跳转
不通过Link/NavLink来设置资源地址跳转,而是通过JS来设置资源地址
-
如果是
Hash模式,那么只需要通过JS设置Hash值即可window.location.hash = '/discover/playlist' -
如果是
History模式如果一个组件是通过路由创建出来的,那么系统就会自动传递一个
history给我们我们只需要拿到这个
history对象,调用这个对象的push方法,通过push方法修改资源地址即可this.props.history.push('/discover/playlist')
注意点:
-
如果一个组件是通过路由创建出来的,那么系统就会自动给这个组件传递一个
history对象但是如果一个组件不是通过路由创建的,那么系统就不会给这个组件传递一个
history对象如果现在非路由创建出来的组件使用
history对象,那么可以借助withRouter高阶组件只要把一个组件传递给
withRouter方法,那么这个方法就会通过路由将传入的组件创建出来 -
如果一个组件要使用路由创建,那么这个组件必须包裹在
BrowserRouter、HashRouter中
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter} from 'react-router-dom';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);// App.js
import React from "react";
import {BrowserRouter, HashRouter, Switch, Route, NavLink, withRouter} from "react-router-dom";
import './App.css';
import Home from "./components/Home";
import About from "./components/About";
import Login from "./components/Login";
import User from "./components/User";
import Discover from "./components/Discover";
function App(props) {
const goPlayList = () => {
// window.location.hash = '/discover/playlist'
props.history.push('/discover/playlist');
}
return (
<div className="App">
<NavLink exact to={'/home'} activeStyle={{color: 'red'}}>home</NavLink>
<NavLink exact to={'/home/about'} activeStyle={{color: 'red'}}>about</NavLink>
<NavLink exact to={'/user'} activeStyle={{color: 'red'}}>user</NavLink>
<NavLink exact to={'/discover'} activeStyle={{color: 'red'}}>广场</NavLink>
<button onClick={() => {
goPlayList()
}}>歌单跳转
</button>
<Switch>
<Route exact path={'/home'} component={Home}></Route>
<Route exact path={'/home/about'} component={About}></Route>
<Route exact path={'/login'} component={Login}></Route>
<Route exact path={'/user'} component={User}></Route>
<Route path={'/discover'} component={Discover}></Route>
</Switch>
</div>
);
}
export default withRouter(App);// Discover.js
import React from 'react';
import {NavLink, Switch, Route} from 'react-router-dom';
function Recommend() {
return (
<div>推荐</div>
)
}
function TopList() {
return (
<div>排行榜</div>
)
}
function PlayList() {
return (
<div>歌单</div>
)
}
class Discover extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isLogin: true
}
}
render() {
return (
<div>
<NavLink exact to={'/discover'} activeStyle={{color: 'red'}}>推荐</NavLink>
<NavLink exact to={'/discover/toplist'} activeStyle={{color: 'red'}}>排行榜</NavLink>
<NavLink exact to={'/discover/playlist'} activeStyle={{color: 'red'}}>歌单</NavLink>
<button onClick={() => {this.goPlayList()}}>歌单跳转</button>
<Switch>
<Route exact path={'/discover'} component={Recommend}></Route>
<Route exact path={'/discover/toplist'} component={TopList}></Route>
<Route exact path={'/discover/playlist'} component={PlayList}></Route>
</Switch>
</div>
)
}
goPlayList(){
// window.location.hash = '/discover/playlist'
this.props.history.push('/discover/playlist');
}
}
export default Discover;路由传参-方式一
// App.js
import React from "react";
import {BrowserRouter, HashRouter, Switch, Route, NavLink, withRouter} from "react-router-dom";
import './App.css';
import Home from "./components/Home";
function App(props) {
const goPlayList = () => {
// window.location.hash = '/discover/playlist'
props.history.push('/discover/playlist');
}
return (
<div className="App">
<NavLink exact to={'/home?msg=hello world!&num=10'} activeStyle={{color: 'red'}}>home</NavLink>
<Switch>
<Route exact path={'/home'} component={Home}></Route>
</Switch>
</div>
);
}
export default withRouter(App);// Home.js
import React from 'react';
class Home extends React.PureComponent {
constructor(props) {
super(props);
let query = this.props.location.search.substring(1);
query = query.split('&');
let obj = {};
query.forEach((item, index) => {
let temp = item.split('=');
obj[temp[0]] = temp[1];
})
console.log(obj, 'obj');
}
render() {
return(
<div>Home</div>
)
}
}
export default Home;路由穿参-方式二
// App.js
import React from "react";
import {BrowserRouter, HashRouter, Switch, Route, NavLink, withRouter} from "react-router-dom";
import './App.css';
import User from "./components/User";
function App(props) {
return (
<div className="App">
<NavLink exact to={'/user/hello world/8'} activeStyle={{color: 'red'}}>user</NavLink>
<Switch>
<Route exact path={'/user/:msg/:num'} component={User}></Route>
</Switch>
</div>
);
}
export default withRouter(App);// User.js
import React from 'react';
import {Redirect} from 'react-router-dom';
class User extends React.PureComponent {
constructor(props) {
super(props);
console.log(props.match.params, 'props');
}
render() {
return (
<div></div>
)
}
}
export default User;路由传参-方式三
// App.js
import React from "react";
import {BrowserRouter, HashRouter, Switch, Route, NavLink, withRouter, useHistory} from "react-router-dom";
import './App.css';
import About from "./components/About";
function App(props) {
let state = {
msg: '你好 世界!',
num: 8
}
return (
<div className="App">
<NavLink exact to={{
pathname: '/home/about',
search: '',
hash: '',
state: state
}} activeStyle={{color: 'red'}}>about</NavLink>
<Switch>
<Route exact path={'/home/about'} component={About}></Route>
</Switch>
</div>
);
}
export default withRouter(App);// About.js
import React from 'react';
class About extends React.PureComponent {
constructor(props) {
super(props);
console.log(props.location.state, 'props');
}
render() {
return(
<div>About</div>
)
}
}
export default About;集中管理
-
通过
react-router-config集中式管理路由信息表npm install react-router-config -S -
新建
router/index.js文件// router/index.js import Home from "../components/Home"; import About from "../components/About"; import Login from "../components/Login"; import User from "../components/User"; import Discover from "../components/Discover"; import {Recommend, TopList, PlayList} from "../components/Discover"; import Page404 from "../components/404"; const Routers = [ { path: '/home', exact: true, component: Home, }, { path: '/home/about', exact: true, component: About, }, { path: '/login', exact: true, component: Login, }, { path: '/user', exact: true, component: User, }, { path: '/discover', component: Discover, routes: [ { path: '/discover', exact: true, component: Recommend, }, { path: '/discover/toplist', exact: true, component: TopList, }, { path: '/discover/playlist', exact: true, component: PlayList, }, ] }, { component: Page404 } ] export default Routers -
在
App.js文件导入renderRoutes和路由配置页// App.js import React from "react"; import {Switch, NavLink, withRouter} from "react-router-dom"; import {renderRoutes } from 'react-router-config' import Routers from "./Router"; import './App.css'; function App(props) { let state = { msg: '你好 世界!', num: 8 } const goPlayList = () => {} return ( <div className="App"> <NavLink exact to={'/home?msg=hello world!&num=10'} activeStyle={{color: 'red'}}>home</NavLink> <NavLink exact to={{ pathname: '/home/about', search: '', hash: '', state: state }} activeStyle={{color: 'red'}}>about</NavLink> <NavLink exact to={'/user/hello world/8'} activeStyle={{color: 'red'}}>user</NavLink> <NavLink exact to={'/discover'} activeStyle={{color: 'red'}}>广场</NavLink> <button onClick={() => { goPlayList() }}>歌单 跳转 </button> <Switch> {renderRoutes(Routers)} </Switch> </div> ); } export default withRouter(App); -
如果有嵌套路由,在嵌套组件下导入
renderRoutes通过
this.props.route.routes获取子路由信息表// Discover.js import React from 'react'; import {NavLink, Switch} from 'react-router-dom'; import { renderRoutes } from 'react-router-config'; export function Recommend() { return ( <div>推荐</div> ) } export function TopList() { return ( <div>排行榜</div> ) } export function PlayList() { return ( <div>歌单</div> ) } class Discover extends React.PureComponent { constructor(props) { super(props); this.state = { isLogin: true } } render() { console.log(this.props.route.routes, '--v'); return ( <div> <NavLink exact to={'/discover'} activeStyle={{color: 'red'}}>推荐</NavLink> <NavLink exact to={'/discover/toplist'} activeStyle={{color: 'red'}}>排行榜</NavLink> <NavLink exact to={'/discover/playlist'} activeStyle={{color: 'red'}}>歌单</NavLink> <button onClick={() => {this.goPlayList()}}>歌单跳转</button> <Switch> {renderRoutes(this.props.route.routes)} </Switch> </div> ) } goPlayList(){ // window.location.hash = '/discover/playlist' this.props.history.push('/discover/playlist'); } } export default Discover;
Redux
Redux是一个管理状态(数据)的容器,提供了可预测的状态管理
- 什么是可预测的状态管理?
数据在什么时候,因为什么,发生了什么改变,都是可以控制和追踪的,我们就称之为预测的状态管理
-
为什么要使用
Redux?-
React是通过数据驱动界面更新的,React负责更新界面,而我们负责管理数据 -
默认情况下我们可以在每个组件中管理自己的状态,但是现在前端应用程序已经变得越来越复杂
状态之间可能存在依赖关系(父子、共享等),一个状态的变化会引起另一个状态的变化
-
所以当应用程序复杂的时候,状态在什么时候改变,因为什么改变,发生了什么改变,就会变得非常难以控制和追踪
-
所以当应用程序复杂的时候,我们想更好的管理、维护、追踪、控制状态时,我们就需要
Redux
-
-
Redux核心理念通过
store来保存数据通过
action来修改数据通过
reducer将store和action串联起来 -
三大原则
-
单一数据源
- 整个应用程序的
state只存储在一个store中 Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护- 单一的数据源可以让整个应用程序的
state变得方便维护、追踪、修改
- 整个应用程序的
-
State是只读的- 唯一修改
State的方法一定是触发action,不要试图在其它地方通过任何的方式来修改State - 这样就确保了
View或网络请求不能直接修改state,它们只能通过action来描述自己想要如何修改state - 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心
race condition
- 唯一修改
-
使用纯函数来执行修改
- 通过
reducer将旧state和action联系在一起,并且返回一个新State - 随着应用程序的复杂度增加,我们可以将
reducer拆分成多个小的reducers,分别操作不同的state tree的一部分 - 但是所有的
reducer都应该是纯函数,不能产生任何的副作用
// 纯函数 function sum(sum1, sum2){ return sum1 + sum2 } const sum3 = 10; function (sum4){ return sum3 + sum4 } // 非纯函数 let sum5 = 10; function (sum6){ return sum5 + sum6 } - 通过
Hook
Hook是React16.8的新特性
它可以让函数式组件拥有类组件特性
-
为什么需要
Hook?在
Hook出现之前,如果我们想在组件中保存自己的状态如果我们想在组件的某个生命周期中做一些事情,那么我们必须使用类组件
-
但是类组件的学习成本是比较高的,你必须懂
ES6的class,你必须懂得箭头函数 -
但是在类组件的同一个生命周期方法中,我们可能会编写很多不同业务逻辑代码
这样就导致了大量不同的业务逻辑代码混杂到一个方法中,导致代码变得很难以维护
(诸如:在组件被挂载的生命周期中,可能主要注册监听,可能需要发送网络请求等)
-
但是在类组件中共享数据是非常繁琐的,需要借助
Context或者Redux等
所以当应用程序变得复杂时,类组件就会变得非常复杂,非常难以维护
所以
Hook就是为了解决以上问题而生的 -
-
如何使用
Hook?Hook的使用我们无需额外安装任何第三方库,因为它就是React的一部分Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用Hook只能在函数最外层调用,不要在循环、条件判断或者子函数中调用
useState
参数:保存状态的初始值
返回值:
是一个数组,这个数组中有两个元素
第一个元素:保存的状态
第二个元素:修改保存状态的方法
// Test.js
import React, {useState} from 'react';
function Test(){
const [ageState, setAgeState] = useState(0);
const [nameState, setNameState] = useState('hello world!');
const [studentState, setStudentState] = useState({
name: 'zs',
age: 14
});
const [heroState, setHeroState] = useState([
{name: '张三', id: 1},
{name: '李四', id: 2},
])
return (
<div>
<p>{ageState}</p>
<button onClick={() => setAgeState(ageState + 1)}>加</button>
<button onClick={() => setAgeState(ageState - 1)}>减</button>
<hr/>
<p>{nameState}</p>
<button onClick={() => setNameState('你好世界!')}>修改</button>
<hr/>
<p>{studentState.name}</p>
<p>{studentState.age}</p>
<hr/>
<ul>
{
heroState.map(item => {
return <li key={item.id}>{item.name}</li>
})
}
</ul>
</div>
)
}
export default Test;userEffect
可以把useEffect Hook看作
componentDidMount(组件挂载时)、componentDidUpdate(组件更新时)、componentWillUnmount(组件卸载时)这三个生命周期函数的组合
特点:
可以设置依赖,只有依赖发生变化的时候才执行
// Test.js
import React, {useState, useEffect} from 'react';
function SubTest(){
const [nameState,setNameState] = useState('hello world!');
const [ageState,setAgeState] = useState(0);
useEffect(() => {
console.log('组件被挂载或者组件更新完成');
return () => {
console.log('组件即将被卸载了');
}
}, [nameState])
return (
<div>
<p>{nameState}</p>
<button onClick={() => {setNameState('你好 世界!')}}>修改</button>
<p>{ageState}</p>
<button onClick={() => {setAgeState(ageState + 1)}}>加</button>
<button onClick={() => {setAgeState(ageState - 1)}}>减</button>
<hr/>
</div>
)
}
function Test(){
const [isShowState, setIsShowState] = useState(false);
return (
<div>
{isShowState && <SubTest/>}
<button onClick={() => setIsShowState(!isShowState)}>切换</button>
</div>
)
}
export default Test;可以书写多个useEffect按顺序执行不同的逻辑
import React, {useState, useEffect} from 'react';
function SubTest(){
const [nameState,setNameState] = useState('hello world!');
const [ageState,setAgeState] = useState(0);
useEffect(() => {
// 组件被挂载
console.log('修改DOM');
})
useEffect(() => {
// 组件被挂载
console.log('注册监听');
return () => {
console.log('移除监听');
}
})
useEffect(() => {
console.log('发送网络请求');
})
return (
<div>
<p>{nameState}</p>
<button onClick={() => {setNameState('你好 世界!')}}>修改</button>
<p>{ageState}</p>
<button onClick={() => {setAgeState(ageState + 1)}}>加</button>
<button onClick={() => {setAgeState(ageState - 1)}}>减</button>
<hr/>
</div>
)
}
function Test(){
const [isShowState, setIsShowState] = useState(false);
return (
<div>
{isShowState && <SubTest/>}
<button onClick={() => setIsShowState(!isShowState)}>切换</button>
</div>
)
}
export default Test;useContext
useContext相当于类组件中的static contextType = Context
// Test.js
import React, {createContext, useContext} from 'react';
const UserContext = createContext({})
const ColorContext = createContext({})
function SubTest(){
const user = useContext(UserContext)
const color = useContext(ColorContext);
return (
<div>
<p>{user.name}</p>
<p>{user.age}</p>
<p>{color.msg}</p>
</div>
)
}
function Test(){
return (
<UserContext.Provider value={{name: '张三', age: 18}}>
<ColorContext.Provider value={{msg: '#fff'}}>
<SubTest></SubTest>
</ColorContext.Provider>
</UserContext.Provider>
)
}
export default Test;useReducer
从名称来看,很多人误以为useReducer是用来替代Redux,但是其实不是
useReducer是useState的一种替代方案,可以让我们很好的复用操作数据的逻辑代码
// App.js
import React, {useState, useReducer} from "react";
import {withRouter} from "react-router-dom";
import './App.css';
function reducer(state, action) {
switch (action.type) {
case 'ADD':
return {...state, num: state.num + 1}
case 'SUB':
return {...state, num: state.num - 1}
}
}
function Home(){
const [state, dispatch] = useReducer(reducer, {num: 0});
return (
<div>
<p>{state.num}</p>
<button onClick={() => {dispatch({type: 'ADD'})}}>加</button>
<button onClick={() => {dispatch({type: 'SUB'})}}>减</button>
</div>
)
}
function About(){
const [state, dispatch] = useReducer(reducer, {num: 0});
return (
<div>
<p>{state.num}</p>
<button onClick={() => {dispatch({type: 'ADD'})}}>加</button>
<button onClick={() => {dispatch({type: 'SUB'})}}>减</button>
</div>
)
}
function App(props) {
return (
<div>
<Home></Home>
<About></About>
</div>
)
}
export default withRouter(App);Ant Design
定制主题
-
安装依赖
npm install @craco/craco -
配置
package.json"scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", + "start": "craco start", + "build": "craco build", + "test": "craco test", } -
安装
craco-lessnpm install craco-less -
引入
antDesign样式// App.js or index.js import 'antd/dist/antd.less'; -
在项目根目录创建一个
craco.config.js并修改配置// craco.config.js const CracoLessPlugin = require('craco-less'); module.exports = { plugins: [ { plugin: CracoLessPlugin, options: { lessLoaderOptions: { lessOptions: { // 配置区域 modifyVars: { '@primary-color': '#1DA57A' }, javascriptEnabled: true, }, }, }, }, ], };
Dva
dva是一个轻量级的应用框架
dva是一个基于redux和redux-saga的数据流方案
内置了react、react-dom、react-router、redux、redux-saga
如何使用
-
安装
dvanpm install dva -
使用
dva-
创建应用
let app = dva(opts) -
配置
hooks或注册插件app.use(hooks) -
注册
modelapp.model(model) -
注册路由表
app.router(history, app) => {routerConfig} -
启动应用
app.start(selector?)
-
// index.js
import dva from 'dva'
// 1.创建实例对象
let app = dva();
// 2.定义组件
function App(){
return (
<div>App</div>
)
}
// 3.注册路由表,告诉dva需要渲染哪个组件
// 回调函数返回的值,就是需要渲染的组件
app.router(() => <App/>)
// 4.启动dva
app.start('#root')管理数据
import dva, {connect} from 'dva'
// 1.创建实例对象
let app = dva();
let homeModel = {
// 指定当前Modal的命名空间名称
// 作用:区分不同的Model
namespace: 'home',
// 指定当前命名空间保存的数据
state: {
num: 0
},
// 指定当前命名空间保存的reducers
reducers: {
add(state, payload) {
return {num: state.num + payload.num}
},
sub(state, payload) {
return {num: state.num - payload.num}
}
}
}
let aboutModel = {
namespace: 'about',
state: {
count: 10
},
reducers: {
add(state, payload) {
return {count: state.count + payload.count}
},
sub(state, payload) {
return {count: state.count - payload.count}
}
}
}
app.model(homeModel);
app.model(aboutModel);
function Home(props){
return (
<div>
{props.num}
<button onClick={() => {props.increment()}}>加</button>
<button onClick={() => {props.decrement()}}>减</button>
</div>
)
}
const mapStateToProps = state => {
return {
num: state.home.num
}
}
const mapDispatchToProps = dispatch => {
return {
increment() {
dispatch({type: 'home/add', num: 2})
},
decrement() {
dispatch({type: 'home/sub', num: 1})
}
}
}
const AdvHome = connect(mapStateToProps, mapDispatchToProps)(Home)
function About(){
return (
<div>123</div>
)
}
// 2.定义组件
function App(){
return (
<div>
<AdvHome></AdvHome>
<About></About>
</div>
)
}
// 3.注册路由表,告诉dva需要渲染哪个组件
// 回调函数返回的值,就是需要渲染的组件
app.router(() => <App/>)
// 4.启动dva
app.start('#root')Umi
Umi是蚂蚁金服的底层前端框架(经蚂蚁内部3000+项目认证)
Umi是以路由为基础的企业级React框架(同时支持配置式路由和约定式路由)
Umi是一个可插拔的企业级React框架(内部功能完全使用插件化完成)
所以Umi是一个蚂蚁金服底层的,以路由为基础的,内部功能完全使用插件话完成的React框架
-
如何使用
npm install uminpx umi g page indexnpx umi dev -
什么叫约定式路由
约定式路由也叫文件路由,就是不需要手写配置路由,通过目录和文件及其命名分析出路由配置
在
Umi中pages目录下面的react组件文件名称即使路由名称
路由配置
// .umirc.js
export default {
routes: [
{ exact: true, path: '/', component: 'index' },
{ exact: true, path: '/home', component: 'home'},
{ exact: true, path: '/about', component: 'about' },
{ exact: true, path: '/user', component: 'user' },
],
}全局布局文件
只要在layoutes目录下新建组件,这个组件就会自动成为其它组件的父组件
// layouts/index.js
import React from 'react';
export default (props) => {
console.log(props, 'props');
return (
<div>
<div>头部</div>
<div>{props.children}</div>
<div>底部</div>
</div>
);
}路由跳转
// index.js
import React from 'react';
import styles from './index.less';
import { Outlet, Link } from 'umi'
const Layout:React.FC = (props) => {
console.log(props, 'props');
return (
<>
<div className={styles.header}>
<Link to={'/home'}>goHome</Link>
<Link to={'/about'}>goPage</Link>
</div>
<div className={styles.main}>
<Outlet></Outlet>
</div>
<div className={styles.footer}>底部</div>
</>
)
}
export default Layout// home.tsx
import React from 'react';
import {Link, history} from 'umi'
console.log(history, 'history');
export default (props) => {
console.log(props, 'props');
return (
<div>
<div>
<Link to={'/home'}>home</Link>
<Link to={'/about'}>about</Link>
<button onClick={() => history.push('/home')}>historyHome</button>
<button onClick={() => history.push('/about')}>historyAbout</button>
</div>
<div>{props.children}</div>
<div>底部</div>
</div>
);
}
动态路由穿参
// pages/home.js
import React from 'react';
import styles from './home.css';
import {Link} from 'umi'
export default function Page() {
return (
<div>
<h1 className={styles.title}>Page home</h1>
<Link to={'/detail/1'}>张三</Link>
<Link to={'/detail/2'}>李四</Link>
</div>
);
}// pages/detail/[id].js
import React from 'react';
export default (props) => {
console.log(props.match.params);
return (
<div>
<p>{props.match.params.id}</p>
</div>
)
}多个路由参数
想传递多个路由参数,就创建多个路由文件夹即可
pages > info > [id] > [name].js
// home.js
import React from 'react';
import styles from './home.css';
import {Link} from 'umi'
export default function Page() {
return (
<div>
<h1 className={styles.title}>Page home</h1>
<Link to={'/detail/1'}>张三</Link>
<Link to={'/detail/2'}>李四</Link>
</div>
);
}
可选路由参数
Umi约定[$]包含的文件或文件夹为动态可选路由
如果路由参数重出现了可选路由参数,那么当参数个数不够时,会自动忽略可选路由参数
pages > info > [id$] > [name].js
import React from 'react';
import styles from './home.css';
import {Link} from 'umi'
export default function Page() {
return (
<div>
<h1 className={styles.title}>Page home</h1>
<Link to={'/detail/1'}>张三</Link>
<Link to={'/detail/2'}>李四</Link>
<Link to={'/info/3/ww'}>王五</Link>
<Link to={'/info/4'}>赵六</Link>
</div>
);
}嵌套路由
umi里约定目录下有_layout.js时会生成嵌套路由
pages > _layout.js, detail.js, index.js, list.js
// pages/_layout.js
import React from 'react';
export default (props) => {
console.log(props);
return (
<div>
<p>子路由页面</p>
<p>{props.children}</p>
</div>
)
}权限路由
在根目录下创建wrappers/auth.js
// wrappers/auth.js
import React from 'react';
import {Redirect} from "umi";
export default (props) => {
const isLogin = true;
if(isLogin){
return <div>{props.children}</div>
}else {
return <Redirect to={'/login'}></Redirect>
}
}在需要使用的页面配置权限
// pages/about.js
import React from 'react';
import styles from './about.css';
import Home from "./home";
const About = () => {
return (
<div>
<h1 className={styles.title}>Page about</h1>
</div>
);
}
About.wrappers = ['@/wrappers/auth']
export default About全局样式
Umi 中约定 src/global.css 为全局样式,如果存在此文件,会被自动引入到入口文件最前面。
// src/global.css
*{
margin: 0;
padding: 0;
}
HTML模版
修改默认模版
新建 src/pages/document.ejs,umi 约定如果这个文件存在,会作为默认模板,比如:
注意点:
- 如果要配置
title,无法在模版中配置,需要动态配置
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Your App</title>
</head>
<body>
<div id="root"></div>
<div id="other"></div>
</body>
</html>// .umirc.js
export default {
title: '标题'
}插件
@umijs/plugin-model
一种基于 hooks 范式的简易数据管理方案(部分场景可以取代 dva),通常用于中台项目的全局共享数据。
// models/useGlobalState.js
import React, {useState} from "react";
export default function useGlobalState(){
const [count, setCount] = useState(10);
const operationObj = {
inc(){
setCount(count + 1);
},
dec(){
setCount(count - 1);
}
}
return [count, operationObj];
}// pages/home.js
import React from 'react';
import styles from './home.css';
import {useModel} from "umi";
const Home = () => {
const [count, {inc, dec }] = useModel('useGlobalState')
return (
<div>
<p>{count}</p>
<button onClick={() => inc()}>加</button>
<button onClick={() => dec()}>减</button>
</div>
);
}
ort default Home// pages/about.js
import React from 'react';
import styles from './about.css';
import { useModel } from 'umi';
const About = () => {
const [count, {inc, dec }] = useModel('useGlobalState')
return (
<div>
<p>{count}</p>
<button onClick={() => inc()}>加</button>
<button onClick={() => dec()}>减</button>
</div>
);
}
export default About@umijs/plugin-initial-state
约定一个地方生产和消费初始化数据。
本插件不可直接使用,必须搭配 @umijs/plugin-model 一起使用。
该配置是一个 async 的 function。会在整个应用最开始执行,返回值会作为全局共享的数据。Layout 插件、Access 插件以及用户都可以通过 useModel('@@initialState') 直接获取到这份数据。
// app.js
async function fetchUser (){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({name: 'zs', age: 18})
}, 3000)
})
}
export async function getInitialState () {
const data = await fetchUser()
return data
}// pages/banner.js
import React from 'react';
import {useModel} from 'umi';
export default function Banner() {
const { initialState, loading, error, refresh, setInitialState } = useModel('@@initialState');
console.log(initialState);
console.log(loading);
return (
<div>
<button onClick={() => {refresh()}}>重新发送请求</button>
</div>
)
}@umijs/plugin-access
有 src/access.ts 时启用。
// access.js
async function fetchUser (){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({name: 'zs', age: 18})
}, 3000)
})
}
export async function getInitialState () {
const data = await fetchUser()
return data
}// pages/about.js
import React from 'react';
import styles from './about.css';
import { Redirect, useModel, useAccess } from 'umi';
const About = () => {
const [count, {inc, dec }] = useModel('useGlobalState');
const access = useAccess();
const send = () => {
console.log('send');
}
if(access.canReadAbout){
return (
<div>
<h1 className={styles.title}>Page about</h1>
<p>{count}</p>
<button onClick={() => inc()}>加</button>
<button onClick={() => dec()}>减</button>
<button disabled={!access.canSendDeleteByUserId} onClick={() => {send()}}>发送</button>
</div>
);
}else {
return <Redirect to={'/login'}></Redirect>
}
}
About.wrappers = ['@/wrappers/auth']
export default About
Hook
Umi Hooks
我们都知道在React 16.8中推出了Hook的新特性,主要为了解决类组件代码分散/不利于管理/不利于维护/不利于复用问题
Umi也是比较积极拥抱变化的,所以Umi作者也封装了一套Hooks的库,来帮助我们提升开发效率
在这套Hooks库中,Umi帮我们封装了常见场景的逻辑代码,让开发变得超级简单
// layouts/index.js
import React from 'react';
import {Button, Switch} from "antd";
import {useBoolean} from "@umijs/hooks";
export default (props) => {
const {state, toggle, setTrue, setFalse} = useBoolean(true)
return (
<div>
<div>
<p>{state + ''}</p>
<Switch checked={state} onChange={() => toggle()}></Switch>
<button onClick={() => setTrue()}>开</button>
<button onClick={() => setFalse()}>关</button>
</div>
</div>
);
}