前言

在 组件的组合、嵌套和组件树 章节内我们实现过一个组件 WeatherDetails

  1. class WeatherDetails extends React.Component {
  2. render() {
  3. return (
  4. <div className="WeatherDetails">
  5. <Details />
  6. <Details />
  7. <Details />
  8. <Details />
  9. <Details />
  10. <Details />
  11. <Details />
  12. <Details />
  13. </div>
  14. );
  15. }
  16. }

可以看出这里我们连续写了8个 Details 组件才达到了一个列表的效果,这种写法肯定是不合适的,这一节我们了解一下怎么优化写法。

前置知识

我们说过 JSX 中,{} 内可放任何 JavaScript 表达式,当然放一个数组也是可以的。

  1. function Demo() {
  2. const components = [
  3. <li>list 1</li>,
  4. <li>list 2</li>,
  5. <li>list 3</li>,
  6. ];
  7. return (
  8. <ul>
  9. {components}
  10. </ul>
  11. );
  12. }

我们在数组内插入了几个 JSX 元素,是可以正常渲染的:

渲染列表数据 - 图1

但是控制台却出现了 warning :

  1. Warning: Each child in an array or iterator should have a unique "key" prop.

数组或迭代器内每个子元素都必须有 prop 属性:”key”。

这是为什么呢?

简单点讲,React 内每次状态变更导致需要更新 Dom 元素时,React 只会去更新需要变更的 Dom 元素,对于没有变化的元素绝对不会动。

回到我们上述的例子,当我们需要将数组内 list 1list2 元素调换位置时,React 会认为他们是分别改变了自己而不是仅仅调换位置,从而导致 React 将两个元素重新渲染,这样无疑加大了 Dom 操作成本。

怎么解决呢?我们给数组内的每一个元素加一个 prop: key,并且保证每个元素的 key 都是自己独有的(就像给每个元素一个身份证)这样 React 就可以根据 key 知道二者仅仅是调换了位置。

实战

为了提高效率降低成本,我们将在 App 组件内发起请求拿到数据,将数据进行简单封装后通过 props 传给各个组件。

首先我们改写 App 组件:

  1. class App extends React.Component {
  2. render() {
  3. return (
  4. <div className="app">
  5. <RealTime />
  6. <WeatherDetails />
  7. <Indexes />
  8. </div>
  9. );
  10. }
  11. }

发送请求,封装数据:

  1. class App extends React.Component {
  2. state = {
  3. realTimeData: null,
  4. weatherDetailsData: null,
  5. indexesData: null,
  6. }
  7. componentDidMount() {
  8. fetch('/app/weather/listWeather?cityIds=101240101')
  9. .then(res => res.json())
  10. .then((res) => {
  11. if (res.code === '200' && res.value.length) {
  12. const { weatherDetailsInfo } = res.value[0];
  13. const { weather3HoursDetailsInfos } = weatherDetailsInfo;
  14. this.setState({
  15. weatherDetailsData: weather3HoursDetailsInfos,
  16. });
  17. }
  18. });
  19. }
  20. render() {
  21. const { weatherDetailsData } = this.state;
  22. return (
  23. <div className="app">
  24. <RealTime />
  25. <WeatherDetails data={weatherDetailsData} />
  26. <Indexes />
  27. </div>
  28. );
  29. }
  30. }

然后我们开始改写 WeatherDetails 及 Details 组件,使用传入的 props 渲染数据:

  1. import Moment from 'moment'; // 这里使用 moment 格式化时间 需要预先安装 `npm i moment --save`
  2. class Details extends React.Component {
  3. render() {
  4. const { data } = this.props;
  5. const time = Moment(data.startTime).format('HH:mm');
  6. const weather = data.weather;
  7. const temperature = `${data.highestTemperature}°`;
  8. return (
  9. <div className="Details">
  10. <div className="time">{time}</div>
  11. <div className="weather">{weather}</div>
  12. <div className="temperature">{temperature}</div>
  13. </div>
  14. );
  15. }
  16. }
  17. class WeatherDetails extends React.Component {
  18. render() {
  19. const { data } = this.props;
  20. return (
  21. <div className="WeatherDetails">
  22. {
  23. // map 返回了一个内容为 JSX 的数组
  24. data && data.map(detail => <Details data={detail} key={detail.startTime}/>)
  25. }
  26. </div>
  27. );
  28. }
  29. }

同样的,对于 Indexes 组件我们也用同样的方式处理,首先修改组件 App:

  1. class App extends React.Component {
  2. state = {
  3. realTimeData: null,
  4. weatherDetailsData: null,
  5. indexesData: null,
  6. }
  7. componentDidMount() {
  8. fetch('/app/weather/listWeather?cityIds=101240101')
  9. .then(res => res.json())
  10. .then((res) => {
  11. if (res.code === '200' && res.value.length) {
  12. const { weatherDetailsInfo, indexes } = res.value[0];
  13. const { weather3HoursDetailsInfos } = weatherDetailsInfo;
  14. this.setState({
  15. weatherDetailsData: weather3HoursDetailsInfos,
  16. indexesData: indexes,
  17. });
  18. }
  19. });
  20. }
  21. render() {
  22. const { weatherDetailsData, indexesData } = this.state;
  23. return (
  24. <div className="app">
  25. <RealTime />
  26. <WeatherDetails data={weatherDetailsData} />
  27. <Indexes data={indexesData}/>
  28. </div>
  29. );
  30. }
  31. }

修改 Indexes 组件:

  1. class Indexes extends React.Component {
  2. render() {
  3. const { data } = this.props;
  4. const Index = ({ data }) => (
  5. <div className="Index">
  6. <div className="level">{data.level}</div>
  7. <div className="name">{data.name}</div>
  8. </div>
  9. );
  10. return (
  11. <div className="Indexes">
  12. {
  13. data && data.map(index => <Index data={index} key={index.abbreviation}/>)
  14. }
  15. </div>
  16. );
  17. }
  18. }

之前写 RealTime 组件时,我们在组件内请求了一次数据,这与 App 内发送的请求重复了,我们重构一下:

  1. // App 组件
  2. class App extends React.Component {
  3. state = {
  4. realTimeData: null,
  5. weatherDetailsData: null,
  6. indexesData: null,
  7. }
  8. componentDidMount() {
  9. fetch('/app/weather/listWeather?cityIds=101240101')
  10. .then(res => res.json())
  11. .then((res) => {
  12. if (res.code === '200' && res.value.length) {
  13. const { realtime, weatherDetailsInfo, indexes } = res.value[0];
  14. const { weather3HoursDetailsInfos } = weatherDetailsInfo;
  15. this.setState({
  16. realTimeData: realtime,
  17. weatherDetailsData: weather3HoursDetailsInfos,
  18. indexesData: indexes,
  19. });
  20. }
  21. });
  22. }
  23. render() {
  24. const { realTimeData, weatherDetailsData, indexesData } = this.state;
  25. return (
  26. <div className="app">
  27. <RealTime data={realTimeData}/>
  28. <WeatherDetails data={weatherDetailsData} />
  29. <Indexes data={indexesData}/>
  30. </div>
  31. );
  32. }
  33. }
  34. // RealTime 组件
  35. class RealTime extends React.Component {
  36. render() {
  37. const {
  38. temp, weather, wD: windType, wS: windLevel, sD: humidity,
  39. } = this.props.data;
  40. return (
  41. <div className="RealTime">
  42. <div className="temp">{temp}</div>
  43. <div className="weather">{weather}</div>
  44. <div className="wind">{`${windType} ${windLevel}`}</div>
  45. <div className="humidity">{`湿度 ${humidity}%`}</div>
  46. </div>
  47. );
  48. }
  49. }

最后

至此,我们的实战已经可以正确渲染列表数据了。下一节,我们将让程序可以根据用户输入的地点查询天气。