Skip to Content
🎉🎉🎉欢迎来到我的空间🎉🎉🎉
前端React

基本使用

<!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>

状态组件

  1. 有状态组件和无状态组件?

    首先要明确的事,组件中的状态(state)指的其实就是数据

    • 有状态组件指的就是有自己数据的组件(逻辑组件)
    • 无状态组件指的就是没有自己数据的组件(展示组件)
  2. 如何定义自己的状态?

    • 凡是继承于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 -g
  • cd 项目名称
  • npm start
  • npm 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()

  1. setState是同步的还是异步的

    默认情况下setState是异步的

  2. 为什么setState是异步的

    主要是为了性能优化

  3. 如何拿到更新之后的数据

    setState方法其实可以接收两个参数

    通过setState方法的第二个参数,通过回调函数拿到

  4. 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;

生命周期

  1. 什么是生命周期

    事物从生到死的过程中,我们称之为生命周期

  2. 什么事生命周期方法

    事物在从生到死过程中,在特定时间节点调用的方法,我们称之为生命周期方法

  3. 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-JSReact中也是一种比较推荐的方式
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 Home

styled-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

  1. 安装react-transition-group

    npm install react-transition-group --save

  2. 从安装好的库中导入CSSTransition

    import {CSSTransition} from 'react-transition-group'

  3. 利用CSSTransition将需要执行过渡效果的组件或元素包裹起来

  4. 编写对应的CSS动画

    实现:.-enter / .-enter-active / .-enter-done / .-exit / .-exit-active / .-exit-done / .-appear / .-appear-active / .-appear-done

  5. 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; }
  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里面的CSSTransitionTransition组件不能像以前那样接收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地址,去渲染不同的组件

基本使用

  1. 如何在React中使用路由

    安装react-router

    npm install react-router-dom

  2. 通过指定监听模式

    BrowserRouter:history模式

    HashRouter:hash模式

  3. 通过Link修改路由URL地址

  4. 通过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/about

    path中的地址:/home

    path中的地址:/home/about

    Route上添加exact那么就可以解决

  • NavLink注意点:

    NavLink在匹配路由的时候,是利用当前资源地址从左至右的和path中的地址进行匹配的

    只要当前资源地址从左至右完整的包含了path中的地址那么就认为匹配

    当前资源地址:/home/about

    path中的地址:/home

    path中的地址:/home/about

    NavLink上添加exact那么就可以解决

  • LinkNavLink注意点:

    默认情况下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方法,那么这个方法就会通过路由将传入的组件创建出来

  • 如果一个组件要使用路由创建,那么这个组件必须包裹在BrowserRouterHashRouter

// 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;

集中管理

  1. 通过react-router-config集中式管理路由信息表

    npm install react-router-config -S

  2. 新建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
  3. 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);
  4. 如果有嵌套路由,在嵌套组件下导入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是一个管理状态(数据)的容器,提供了可预测的状态管理

  1. 什么是可预测的状态管理?

数据在什么时候,因为什么,发生了什么改变,都是可以控制和追踪的,我们就称之为预测的状态管理

  1. 为什么要使用Redux

    • React是通过数据驱动界面更新的,React负责更新界面,而我们负责管理数据

    • 默认情况下我们可以在每个组件中管理自己的状态,但是现在前端应用程序已经变得越来越复杂

      状态之间可能存在依赖关系(父子、共享等),一个状态的变化会引起另一个状态的变化

    • 所以当应用程序复杂的时候,状态在什么时候改变,因为什么改变,发生了什么改变,就会变得非常难以控制和追踪

    • 所以当应用程序复杂的时候,我们想更好的管理、维护、追踪、控制状态时,我们就需要Redux

  2. Redux核心理念

    通过store来保存数据

    通过action来修改数据

    通过reducerstoreaction串联起来

  3. 三大原则

  • 单一数据源

    • 整个应用程序的state只存储在一个store
    • Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护
    • 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改
  • State是只读的

    • 唯一修改State的方法一定是触发action,不要试图在其它地方通过任何的方式来修改State
    • 这样就确保了View或网络请求不能直接修改state,它们只能通过action来描述自己想要如何修改state
    • 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition
  • 使用纯函数来执行修改

    • 通过reducer将旧stateaction联系在一起,并且返回一个新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

HookReact16.8的新特性

它可以让函数式组件拥有类组件特性

  1. 为什么需要Hook?

    Hook出现之前,如果我们想在组件中保存自己的状态

    如果我们想在组件的某个生命周期中做一些事情,那么我们必须使用类组件

    • 但是类组件的学习成本是比较高的,你必须懂ES6class,你必须懂得箭头函数

    • 但是在类组件的同一个生命周期方法中,我们可能会编写很多不同业务逻辑代码

      这样就导致了大量不同的业务逻辑代码混杂到一个方法中,导致代码变得很难以维护

      (诸如:在组件被挂载的生命周期中,可能主要注册监听,可能需要发送网络请求等)

    • 但是在类组件中共享数据是非常繁琐的,需要借助Context或者Redux

    所以当应用程序变得复杂时,类组件就会变得非常复杂,非常难以维护

    所以Hook就是为了解决以上问题而生的

  2. 如何使用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,但是其实不是

useReduceruseState的一种替代方案,可以让我们很好的复用操作数据的逻辑代码

// 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

定制主题

  1. 安装依赖

    npm install @craco/craco

  2. 配置package.json

    "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", + "start": "craco start", + "build": "craco build", + "test": "craco test", }
  3. 安装craco-less

    npm install craco-less

  4. 引入antDesign样式

    // App.js or index.js import 'antd/dist/antd.less';
  5. 在项目根目录创建一个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是一个基于reduxredux-saga的数据流方案

内置了react、react-dom、react-router、redux、redux-saga

如何使用

  • 安装dva

    npm install dva

  • 使用dva

    • 创建应用

      let app = dva(opts)

    • 配置hooks或注册插件

      app.use(hooks)

    • 注册model

      app.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 umi npx umi g page index

    npx umi dev

  • 什么叫约定式路由

    约定式路由也叫文件路由,就是不需要手写配置路由,通过目录和文件及其命名分析出路由配置

    Umipages目录下面的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> ); }