Skip to content

antd-mobile 使用

bash
npm i antd-mobile
# https://antd-mobile-v2.surge.sh/components/tab-bar-cn/#components-tab-bar-demo-basic

路由嵌套

js
// 准备父路由
function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Route path="/home" component={Home}></Route>
      </div>
    </BrowserRouter>
  )
}
js
// 配置子路由
import React from 'react'
import News from '../News'
import { Route } from 'react-router-dom'
export default class Home extends React.Component {
  render() {
    return (
      <div>
        首页
        <Route path="/home/news" component={News}></Route>
      </div>
    )
  }
}

路由重定向

js
// 导入组件
// import { Button } from "antd-mobile";
import { BrowserRouter, Route, Redirect } from 'react-router-dom'
import Home from './pages/Home'
import CityList from './pages/CityList'
function App() {
  return (
    <BrowserRouter>
      <div className="App">
        {/* Redirect 可用于路由重定向,如下写法可实现默认路由,当访问 / 路径时自动重定向到 home  */}
        <Route path="/" render={() => <Redirect to="/home" />}></Route>
        <Route path="/home" component={Home}></Route>
      </div>
    </BrowserRouter>
  )
}

在钩子函数中获取路由变化

js
// 这样就能获取到变化前后的路由信息
componentDidUpdate(prevProps) {
    console.log(this.props);
    console.log(prevProps);
}

配置 css 预编译语言

bash
# 由于 react 脚手架内置了 sass-loader 所以只需要安装 sass 即可,sass是以 .scss结尾的文件
npm install sass # 安装sass

百度地图 API

js
// 百度地图开放平台
https://lbsyun.baidu.com/
// 开发文档
https://lbsyun.baidu.com/index.php?title=jspopularGL/guide/geoloaction
// api 文档
https://mapopen-pub-jsapi.bj.bcebos.com/jsapi/reference/jsapi_webgl_1_0.html

1.在 index.html 中引入 js 文件

html
<script
  type="text/javascript"
  src="https://api.map.baidu.com/apiv=1.0&&type=webgl&a
k=Ql6g9FMhKC8AQhzbPG7erzSwCEAMmCCP"
></script>

2.在组件中使用

js
import React from 'react'
import './index.scss'
export default class Map extends React.Component {
  componentDidMount() {
    // 初始化地图实例
    // 在 react 脚手架中全局对象需要使用 window 来访问,否则会造成 ESlint 校验错误
    const map = new window.BMapGL.Map('container')
    // 设置中心点坐标
    const point = new window.BMapGL.Point(116.404, 39.915)
    // 初始化地图 centerAndZoom 第二个参数用于设置地图展示(放大)级别
    map.centerAndZoom(point, 15)
  }
  render() {
    return (
      <div className="map">
        {/* 地图容器元素 */}
        <div id="container">zxczxc</div>
      </div>
    )
  }
}

通过 ip 获取当前城市名

js
const curCity = new window.BMapGL.LocalCity()
curCity.get(res => {
  console.log(res)
})

通过城市名转换为坐标并渲染

js
import React from 'react'
import './index.scss'
import NavHeader from '../../components/NavHeader'
export default class Map extends React.Component {
  componentDidMount() {
    const { label, value } = JSON.parse(sessionStorage.getItem('hkzf_city'))
    const map = new window.BMapGL.Map('container')
    //创建地址解析器实例
    const myGeo = new window.BMapGL.Geocoder()
    // 将地址解析结果显示在地图上,并调整地图视野
    // getPoint 第一个参数表示需要转换为坐标的地区名称,最后一个参数需要指定地址解析所在的城市
    myGeo.getPoint(
      label,
      point => {
        if (point) {
          // 通过参数 point 得到转换后的坐标初始化地图和缩放级别
          map.centerAndZoom(point, 16)
          // map.addOverlay 表示在地图标记点添加一个标记物
          // map.addOverlay(new window.BMapGL.Marker(point));
        }
      },
      label
    )
  }
  render() {
    return (
      <div className="map">
        <NavHeader title="地图找房" />
        {/* 地图容器元素 */}
        <div id="container"></div>
      </div>
    )
  }
}

添加控件

js
import React from 'react'
import './index.scss'
import NavHeader from '../../components/NavHeader'
export default class Map extends React.Component {
  componentDidMount() {
    const { label, value } = JSON.parse(sessionStorage.getItem('hkzf_city'))
    const map = new window.BMapGL.Map('container')
    //创建地址解析器实例
    const myGeo = new window.BMapGL.Geocoder()
    // 将地址解析结果显示在地图上,并调整地图视野
    // getPoint 第一个参数表示需要转换为坐标的地区名称,最后一个参数需要指定地址解析所在的城市
    myGeo.getPoint(
      label,
      point => {
        if (point) {
          // 初始化地图和缩放级别
          map.centerAndZoom(point, 11)
          // 添加比例尺控件////////////////////////////////////////////////////////////
          map.addControl(new window.BMapGL.ScaleControl())
          // 添加缩放控件////////////////////////////////////////////////////////////
          map.addControl(new window.BMapGL.ZoomControl())
        }
      },
      label
    )
  }
  render() {
    return (
      <div className="map">
        <NavHeader title="地图找房" />
        {/* 地图容器元素 */}
        <div id="container"></div>
      </div>
    )
  }
}

添加文本标注

js
import React from 'react'
import './index.scss'
import NavHeader from '../../components/NavHeader'
export default class Map extends React.Component {
  initMap() {
    const { label, value } = JSON.parse(sessionStorage.getItem('hkzf_city'))
    const map = new window.BMapGL.Map('container')
    //创建地址解析器实例
    const myGeo = new window.BMapGL.Geocoder()
    // 将地址解析结果显示在地图上,并调整地图视野
    // getPoint 第一个参数表示需要转换为坐标的地区名称,最后一个参数需要指定地址解析所在的城市
    myGeo.getPoint(
      label,
      point => {
        if (point) {
          // 初始化地图和缩放级别
          map.centerAndZoom(point, 11)
          // 添加比例尺控件
          map.addControl(new window.BMapGL.ScaleControl())
          // 添加缩放控件
          map.addControl(new window.BMapGL.ZoomControl())
          // 创建文本标注对象 第二个参数表示配置对象////////////////////////////////////////
          const ops = {
            position: point, // 文本标注显示位置
            offset: new window.BMapGL.Size(30, -30) // 设置偏移量
          }
          const label = new window.BMapGL.Label('测试文本!', ops)
          // 自定义文本标注样式
          label.setStyle({
            color: 'blue'
            // borderRadius: "5px",
            // borderColor: "#ccc",
            // padding: "10px",
            // fontSize: "16px",
            // height: "30px",
            // lineHeight: "30px",
            // fontFamily: "微软雅黑",
          })
          // 将配置好的文本标注添加到地图中
          map.addOverlay(label)
          ///////////////////////////////////////////////////////////////////////
        }
      },
      label
    )
  }
  componentDidMount() {
    this.initMap()
  }
  render() {
    return (
      <div className="map">
        <NavHeader title="地图找房" />
        {/* 地图容器元素 */}
        <div id="container"></div>
      </div>
    )
  }
}

自定义覆盖物

js
import React from 'react'
import './index.scss'
import styles from './index.module.css'
import NavHeader from '../../components/NavHeader'
export default class Map extends React.Component {
  initMap() {
    const { label, value } = JSON.parse(sessionStorage.getItem('hkzf_city'))
    const map = new window.BMapGL.Map('container')
    //创建地址解析器实例
    const myGeo = new window.BMapGL.Geocoder()
    // 将地址解析结果显示在地图上,并调整地图视野
    // getPoint 第一个参数表示需要转换为坐标的地区名称,最后一个参数需要指定地址解析所在的城市
    myGeo.getPoint(
      label,
      point => {
        if (point) {
          // 初始化地图和缩放级别
          map.centerAndZoom(point, 11)
          // 添加比例尺控件
          map.addControl(new window.BMapGL.ScaleControl())
          // 添加缩放控件
          map.addControl(new window.BMapGL.ZoomControl())
          // 创建文本标注对象 第二个参数表示配置对象
          const ops = {
            position: point // 文本标注显示位置
            // offset: new window.BMapGL.Size(30, -30), // 设置偏移量
          }
          // 设置 setContent 后 BMapGL.Label 的第一个参数设置文本内容就被覆盖掉了
          const label = new window.BMapGL.Label('', ops)
          ///////////////////////////////////////////////////////////////////////
          // 用于自定义文本标注覆盖物显示内容,传递一段html代码
          label.setContent(`<div class="${styles.bubble}">
          <p class="${styles.name}">浦东</p>
          <p>98套</p>
          </div>`)
          // 自定义文本标注样式
          label.setStyle({
            cursor: 'pointer',
            border: '0px solid rgb(255,0,0)',
            padding: '0',
            whiteSpace: 'normal',
            fontSize: '12px',
            color: 'rgb(252, 228, 228)',
            textAlign: 'center'
          })
          // 添加点击事件
          label.addEventListener('click', () => {
            console.log('点击')
          })
          ///////////////////////////////////////////////////////////////////////
          // 将配置好的文本标注添加到地图中
          map.addOverlay(label)
        }
      },
      label
    )
  }
  componentDidMount() {
    this.initMap()
  }
  render() {
    return (
      <div className="map">
        <NavHeader title="地图找房" />
        {/* 地图容器元素 */}
        <div id="container"></div>
      </div>
    )
  }
}
css
.bubble {
  width: 70px;
  height: 70px;
  line-height: 1;
  display: inline-block;
  position: absolute;
  border-radius: 100%;
  background-color: #0ad38b;
  color: #fff;
  border: 2px solid rgba(255, 255, 255, 0.8);
  text-align: center;
  cursor: pointer;
}
.name {
  padding: 18px 0 6px 0;
}

点击覆盖物放大并定位

js
import React from 'react'
import './index.scss'
import styles from './index.module.css'
import axios from 'axios'
import NavHeader from '../../components/NavHeader'
export default class Map extends React.Component {
  initMap() {
    const { label, value } = JSON.parse(sessionStorage.getItem('hkzf_city'))
    const map = new window.BMapGL.Map('container')
    //创建地址解析器实例
    const myGeo = new window.BMapGL.Geocoder()
    // 将地址解析结果显示在地图上,并调整地图视野
    // getPoint 第一个参数表示需要转换为坐标的地区名称,最后一个参数需要指定地址解析所在的城市
    myGeo.getPoint(
      label,
      async point => {
        if (point) {
          // 初始化地图和缩放级别
          map.centerAndZoom(point, 11)
          // 添加比例尺控件
          map.addControl(new window.BMapGL.ScaleControl())
          // 添加缩放控件
          map.addControl(new window.BMapGL.ZoomControl())
          // 创建文本标注对象 第二个参数表示配置对象
          // const ops = {
          //   position: point, // 文本标注显示位置
          //   offset: new window.BMapGL.Size(-35, -35), // 设置偏移量
          // };
          // 创建多个覆盖物
          //////////////////////////////////////////////////////////////////////
          // 获取房源信息
          const { data } = await axios.get(
            `http://localhost:8080/area/map?id=${value}`
          )
          data.body.forEach(item => {
            const areaPoint = new window.BMapGL.Point(
              item.coord.longitude,
              item.coord.latitude
            )
            const label = new window.BMapGL.Label('', {
              position: areaPoint, // 文本标注显示位置
              offset: new window.BMapGL.Size(-35, -35) // 设置偏移量
            })
            // 用于自定义文本标注覆盖物显示内容,传递一段html代码
            label.setContent(`<div class="${styles.bubble}">
            <p class="${styles.name}">${item.label}</p>
            <p>${item.count}套</p>
            </div>`)
            // 自定义文本标注样式
            label.setStyle({
              cursor: 'pointer',
              border: '0px solid rgb(255,0,0)',
              padding: '0',
              whiteSpace: 'normal',
              fontSize: '12px',
              color: 'rgb(252, 228, 228)',
              textAlign: 'center'
            })
            // 添加点击事件
            label.addEventListener('click', () => {
              // 第一个参数表示放大区域的中心坐标,第二个参数表示放大级别
              map.centerAndZoom(areaPoint, 13)
              // 清除页面上所有的覆盖物
              map.clearOverlays()
            })
            // 将配置好的文本标注添加到地图中
            map.addOverlay(label)
          })
          //////////////////////////////////////////////////////////////////////
        }
      },
      label
    )
  }
  componentDidMount() {
    this.initMap()
  }
  render() {
    return (
      <div className="map">
        <NavHeader title="地图找房" />
        {/* 地图容器元素 */}
        <div id="container"></div>
      </div>
    )
  }
}

获取当前点击覆盖物坐标并移动地图

js
// 绘制镇覆盖物
createRect(point, name, count, id) {
    const label = new window.BMapGL.Label("", {
        position: point, // 文本标注显示位置
        offset: new window.BMapGL.Size(-50, -28), // 设置偏移量
    });
    label.setContent(`<div class="${styles.rect}">
<span class="${styles.housename}">${name}</span>
<span class="${styles.housenum}">${count}套</span>
<i class="${styles.arrow}"></i>
</div>`);
    // 自定义文本标注样式
    label.setStyle({
        cursor: "pointer",
        border: "0px solid rgb(255,0,0)",
        padding: "0",
        whiteSpace: "normal",
        fontSize: "12px",
        color: "rgb(252, 228, 228)",
        textAlign: "center",
    });
    // 添加点击事件
    label.addEventListener("click", (e) => {
        //////////////////////////////////////////////////
        console.log(e.domEvent.changedTouches[0]);
      // panBy 方法用于移动地图位置,第一个参数表示 x 轴方向需要移动的距离,第二个表示 y 轴需要移动的距离
      // 这里用于移动到屏幕中心位置
        this.map.panBy(
        window.innerWidth / 2 - e.domEvent.changedTouches[0].clientX,
        (window.innerHeight - 300) / 2 - e.domEvent.changedTouches[0].clientY
      );
        //////////////////////////////////////////////////
        this.getHousesList(id);
    });
    // 将配置好的文本标注添加到地图中
    this.map.addOverlay(label);
}

给地图添加地图移动事件

js
// 初始化地图
  initMap() {
    const { label, value } = JSON.parse(sessionStorage.getItem("hkzf_city"));
    const map = new window.BMapGL.Map("container");
    // 保存地图实例供其他地方使用
    this.map = map;
    //创建地址解析器实例
    const myGeo = new window.BMapGL.Geocoder();
    // 将地址解析结果显示在地图上,并调整地图视野
    // getPoint 第一个参数表示需要转换为坐标的地区名称,最后一个参数需要指定地址解析所在的城市
    myGeo.getPoint(
      label,
      async (point) => {
        if (point) {
          // 初始化地图和缩放级别
          map.centerAndZoom(point, 11);
          // 添加比例尺控件
          map.addControl(new window.BMapGL.ScaleControl());
          // 添加缩放控件
          map.addControl(new window.BMapGL.ZoomControl());
          // 渲染覆盖物入口
          this.renderOverlays(value);
          ///////////////////////////////////////////////////////////////////////////
          // 给地图添加移动事件,可以监听用户手指或鼠标拖动地图事件
          map.addEventListener("movestart", () => {
            console.log("55555555555555555555555555555");
            this.setState({
              isshow: false,
            });
          });
          ///////////////////////////////////////////////////////////////////////////
        }
      },
      label
    );
  }

长列表

js
// 使用 react-virtualized 实现长列表渲染
// 原理:只渲染页面可视区域的内容,可视区域之外的不渲染,提高性能
bash
npm i react-virtualized # 安装
js
// 在 index.js 中导入样式文件
import 'react-virtualized/styles.css'
js
// 打开文档找到需要使用的组件
https://github.com/imkf-zhang/react-virtualized/blob/master/docs/README.md
js
import {List} from 'react-virtualized';
// 列表数据
const list = new Array(100).fill("测试一下");
// 函数返回值就是列表最终展示的内容
function rowRenderer({
  key, // 渲染时的唯一 key 值
  index, // 每条数据的索引号
  isScrolling, // 表示当前项是不是在滚动中
  isVisible, // 当前项在列表中是否可见
  style, // 表示样式对象,将来会被应用到每一项,且必须添加该属性,指定每一行的位置
}) {
  return (
    <div key={key} style={style}>
      {list[index]}
    </div>
  );
}
export default class CityList extends React.Component {
render() {
    return (
      <div className="citylist">
        {/* 城市列表 */}
        <List
          width={300}
          height={300}
		  {/* rowCount 指定当前列表行数 */}
          rowCount={list.length}
		  {/* rowHeight 指定每行的高度 */}
          rowHeight={20}
          rowRenderer={rowRenderer}
        />
      </div>
    );
  }
}

让列表占满屏幕

js
import { List, AutoSizer } from 'react-virtualized'
const list = new Array(100).fill('测试一下')
function rowRenderer({
  key, // 渲染时的唯一 key 值
  index, // 每条数据的索引号
  isScrolling, // 表示当前项是不是在滚动中
  isVisible, // 当前项在列表中是否可见
  style // 表示样式对象,将来会被应用到每一项,且必须添加该属性,指定每一行的位置
}) {
  return (
    <div key={key} style={style}>
      {list[index]}
    </div>
  )
}

export default class CityList extends React.Component {
  render() {
    return (
      <div className="citylist">
        {/* 城市列表 */}
        {/* 使用 AutoSizer 组件时该组件父元素必须有高度 */}
        <AutoSizer>
          {({ width, height }) => (
            <List
              width={width}
              height={height}
              rowCount={list.length}
              rowHeight={20}
              rowRenderer={rowRenderer}
            />
          )}
        </AutoSizer>
      </div>
    )
  }
}

动态设置每行的高度

js
// 可以通过解构出来的 index ,表示当前行的索引
getRowHeight = ({ index }) => {
    return (this.state.cityList[this.state.cityIndex[index]].length + 1) * 50;
};
render() {
    return (
        <div className="citylist">
            {/* 城市列表 */}
            {/* 使用 AutoSizer 组件时该组件父元素必须有高度 */}
            <AutoSizer>
                {({ width, height }) => (
                    <List
                        width={width}
                        height={height}
                        rowCount={this.state.cityIndex.length}
                        {/* rowHeight 可以为一个函数,可以从其形参中解构出当前项索引 */}
                        rowHeight={this.getRowHeight}
                        rowRenderer={this.rowRenderer}
                        />
                )}
            </AutoSizer>
        </div>
    );
}

获取当前页面列表顶部 item 项索引

js
// 获取当前列表顶部 item 项索引
onRowsRendered = ({ startIndex }) => {
    console.log(startIndex);
};
render() {
    return (
        <div className="citylist">
            <AutoSizer>
                {({ width, height }) => (
                    <List
                        width={width}
                        height={height}
                        rowCount={this.state.cityIndex.length}
                        rowHeight={this.getRowHeight}
                        rowRenderer={this.rowRenderer}
                        onRowsRendered={this.onRowsRendered}
                        />
                )}
            </AutoSizer>
        </div>
    );
}

实现点击跳转

js
  constructor(props) {
    super(props);
     ////////////////////////////////////////////////////////////////////
    // 创建 ref 对象 用于获取页面元素
    this.cityListComponent = React.createRef();
      ////////////////////////////////////////////////////////////////////
  }
  async getCityList() {
    const res = await axios.get(`http://localhost:8080/area/city?level=1`);
    const obj = {};
    res.data.body.forEach((item) => {
      if (obj[item.short.slice(0, 1)]) {
        obj[item.short.slice(0, 1)].push(item);
      } else {
        obj[item.short.slice(0, 1)] = [item];
      }
    });
    const citystr = Object.keys(obj).sort();
    citystr.unshift("hot");
    citystr.unshift("#");
    const res2 = await axios.get(`http://localhost:8080/area/hot`);
    obj.hot = res2.data.body;
    const label = await getCurrentCity();
    obj["#"] = [label];
      ////////////////////////////////////////////////////////////////////
    this.setState(
      {
        cityList: obj,
        cityIndex: citystr,
      },
      () => {
        // 调用 me 提前计算 List 中每一行的高度,解决跳转时精度偏差问题
        this.cityListComponent.current.measureAllRows();
      }
    );
      ////////////////////////////////////////////////////////////////////
  }
renderCityIndex() {
    return this.state.cityIndex.map((item, i) => (
        ////////////////////////////////////////////////////////////////////
      <li
        className="city-index-item"
        key={item}
        onClick={() => {
          // 注意 scrollToRow 方法只能在已经在页面上渲染过一次的 item 项中跳转,否则会出现位置偏差问题
          this.cityListComponent.current.scrollToRow(i);
        }}
      >
        <span className={i === this.state.activeIndex ? "index-active" : ""}>
          {item === "hot" ? "热" : item.toUpperCase()}
        </span>
      </li>
        ////////////////////////////////////////////////////////////////////
    ));
  }
render() {
    return (
      <div className="citylist">
        <AutoSizer>
          {({ width, height }) => (
            <List
              ref={this.cityListComponent}
              width={width}
              height={height}
              rowCount={this.state.cityIndex.length}
              rowHeight={this.getRowHeight}
              rowRenderer={this.rowRenderer}
              onRowsRendered={this.onRowsRendered}
              // 当调用 onRowsRendered 跳转时始终保持跳转项出现在页面顶部
              scrollToAlignment="start"
            />
          )}
        </AutoSizer>
        {/* 右侧索引列表 */}
        <ul className="city-index">{this.renderCityIndex()}</ul>
      </div>
    );
  }

使用 WindowScroller 高阶组件跟随页面滚动

js
// 默认情况下 react-virtualized 生成的列表会自己生成滚动条而不会跟随页面滚动
// github.com/imkf-zhang/react-virtualized/blob/master/docs/WindowScroller.md
js
export default class HouseList extends React.Component {
  // 渲染列表每一行
  rowRenderer = ({ key, index, style }) => {
    return (
      <HouseItem
        key={key}
        style={style}
        src={'http://localhost:8080' + this.state.list[index].houseImg}
        title={this.state.list[index].title}
        desc={this.state.list[index].desc}
        tags={this.state.list[index].tags}
        price={this.state.list[index].price}
      />
    )
  }
  render() {
    return (
      <div>
        <div className={styles.houseItems}>
          {/* 结合 AutoSizer 和 WindowScroller 实现跟随页面滚动并且设置全屏高宽*/}
          <WindowScroller>
            {({ height, isScrolling, scrollTop }) => (
              <AutoSizer>
                {({ width }) => (
                  <List
                    autoHeight // 设置高度为 WindowScroller 最终渲染的列表高度
                    width={width} // 视口宽度
                    height={height} // 视口高度
                    rowCount={this.state.count} // list 列表项的行数
                    rowHeight={120} // 每一行的高度
                    rowRenderer={this.rowRenderer} // 渲染列表项中的每一行
                    isScrolling={isScrolling} // 指示表格或列表是否正在滚动
                    scrollTop={scrollTop} // 从页面滚动距离
                  />
                )}
              </AutoSizer>
            )}
          </WindowScroller>
        </div>
      </div>
    )
  }
}

样式隔离

css in js

js
// 是使用 js 编写 css 的统称,用来解决 css 样式冲突、覆盖的问题
// css in js 具体实现有许多种,其中比较常用的有 css Modules、style-components
// 其中 css Modules 在 React 脚手架中已经集成,可直接使用

css Modules 使用

js
// css Modules 是通过对 css 重命名保证类名的唯一性来避免样式冲突的问题
// 重命名采用:BEM(Block块、Element 元素、Modifier)如:.list__item_active
// 在 react 脚手架中演化成:文件名、类名、hash(随机值)

创建 module.css 文件

在组件中引入并使用

空标签使用

js
// 由于 react 组件默认只能返回一个根节点,但如果需要同时返回多个节点,在最外层加一层 div 也可以,但会使项目多一层无意义的标签元素,这时可以使用空标签解决,类似于 vue 里的 template 标签
// 空标签实际上是 <React.Fragment></React.Fragment> 的简化语法,作用是不添加额外元素返回多个节点

动态添加样式和 ref 的使用

js
import { createRef, useRef } from 'react'
class Sticky extends Component {
  // 创建ref对象
  placeholder = createRef()
  // 还可以使用 ReactHoos 的 useRef 实现
  // placeholder = useRef();
  content = createRef()

  // scroll 事件的事件处理程序
  handleScroll = () => {
    // 获取DOM对象
    const placeholderEl = this.placeholder.current
    const contentEl = this.content.current

    const { top } = placeholderEl.getBoundingClientRect()
    if (top < 0) {
      // 吸顶
      // 添加class
      contentEl.classList.add(styles.fixed)
      placeholderEl.style.height = '40px'
    } else {
      // 取消吸顶
      // 移除class
      contentEl.classList.remove(styles.fixed)
      placeholderEl.style.height = '0px'
    }
  }

  // 监听 scroll 事件
  componentDidMount() {
    document
      .getElementById('home')
      .addEventListener('scroll', this.handleScroll)
  }

  componentWillUnmount() {
    document
      .getElementById('home')
      .removeEventListener('scroll', this.handleScroll)
  }

  render() {
    return (
      <div>
        {/* 占位元素 */}
        <div ref={this.placeholder} />
        {/* 内容元素 */}
        <div ref={this.content}>{this.props.children}</div>
      </div>
    )
  }
}

react-spring 动画库

js
// https://react-spring.io/ 官方文档
// yarn add react-spring 安装
js
// 基本使用
// 导入 spring 组件
import { Spring, animated } from "react-spring";
export default class Filter extends Component {
    renderMask() {
    const flag = this.state.openType !== "more" && this.state.openType !== "";
    return (
        // from 指定初始样式(第一次渲染),to 表示选然后样式,这里通过改变 to 的样式实现淡入淡出动画
      <Spring from={{ opacity: 0 }} to={{ opacity: flag ? 1 : 0 }}>
        {(props) => {
          if (!flag) return "";
          return (
            <animated.div style={props}>
              <div className={styles.mask} onClick={this.onCancel} />
            </animated.div>
          );
        }}
      </Spring>
    );
  }
    render() {
        return (
            <div className={styles.root}>
                {this.renderMask()}
                );
                }
}

路由参数

js
// 定义路由参数
function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Route path="/home" component={Home}></Route>
        <Route path="/citylist" component={CityList}></Route>
        <Route path="/map" component={Map}></Route>
        {/* id 就表示要动态捕获的路由参数 */}
        <Route path="/detail/:id" component={HouseDetail}></Route>
        {/* 路由参数后加上 ? 表示参数可选 */}
        <Route path="/detail2/:id?" component={HouseDetail2}></Route>
        <Route path="/" exact render={() => <Redirect to="/home" />}></Route>
      </div>
    </BrowserRouter>
  )
}

// 在组件中获取
export default class HouseDetail extends Component {
  componentDidMount() {
    console.log(this.props.match.params)
  }
}

页面访问控制

js
// 封装 AuthRoute 组件
import React from 'react'
import { Route, Redirect } from 'react-router-dom'
// <AuthRoute path="..." component={...}></AuthRoute>
const AuthRoute = ({ component: Component, ...rest }) => {
  return (
    <Route
      {...rest}
      render={props => {
        if (sessionStorage.getItem('hkzf_token')) {
          // 已登录
          // 将 props 传递给组件,组件中才能获取到路由相关信息
          return <Component {...props} />
        } else {
          // 未登录
          return (
            // Redirect 重定向组件,to 属性指定需要重定向的地址,state 表示需要传递的数据,这里将路由信息传递给 /login 对应的组件,该组件可以通过 props.location.state 拿到数据
            <Redirect
              to={{
                pathname: '/login',
                state: {
                  from: props.location
                }
              }}
            />
          )
        }
      }}
    />
  )
}
export default AuthRoute
js
// 使用该组件
// 这样就能实现再未登录时访问 map 页面会重定向回登录页
import AuthRoute from './components/AuthRoute'
function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <AuthRoute path="/map" component={Map}></AuthRoute>
      </div>
    </BrowserRouter>
  )
}

export default App

最简单的实现

js
import styles from './App.module.css'
import { Home, Login, Detail, Search, Shopping } from './page'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import { Redirect } from 'react-router-dom'
import { useSelector } from './redux/hooks'

// 创建页面访问控制组件,isAuthenticated 表示权限标识
const PrivateRoute = ({ isAuthenticated, ...rest }) => {
  if (isAuthenticated) {
    return <Route {...rest} />
  } else {
    return <Redirect to={{ pathname: 'signIn' }} />
  }
}

function App() {
  // 获取权限标识
  const jwt = useSelector(s => s.UserState.token)
  return (
    <div className={styles.App}>
      <BrowserRouter>
        {/* 使用组件 */}
        <PrivateRoute
          path="/shopping"
          component={Shopping}
          isAuthenticated={jwt}
        />
      </BrowserRouter>
    </div>
  )
}
export default App

路由传参

js
onTipsClick = ({ communityName, community }) => {
  // 通过 replace 或者 push 跳转时可以添加第二个参数表示需要传递的参数,这种方式传递的参数不在地址栏显示且刷新还在
  this.props.history.replace('/rent/add', {
    name: communityName,
    id: community
  })
}

// 在跳转的页面中获取数据
export default class RentAdd extends Component {
  constructor(props) {
    super(props)
    console.log(props)
  }
}

组件懒加载

js
// 导入组件 lazy
// Suspense 组件用于当异步组件未加载完成时显示(loading)fallback指定显示内容
import { lazy, Suspense } from 'react'
// 这种导入方法也行
// import React, { lazy } from "react";

// 指定懒加载组件
const News = lazy(() => import('../News'))
function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>loading...</div>}>
        <div className="App">
          <Route path="/News" component={News}></Route>
        </div>
      </Suspense>
    </BrowserRouter>
  )
}