9

通过React16.8构建新闻网站第三章:新闻主页的构建

 3 years ago
source link: https://zhuanlan.zhihu.com/p/248561382
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

通过React16.8构建新闻网站第三章:新闻主页的构建

北京奇观技术有限责任公司 软件开发工程师

专栏地址:

第三章为网站主页的构建

实现效果图如下:

1 构建主页的layout

因为主页面的数据交互比较复杂 所以根据布局 将主页面拆为四个组件IndexLeft、IndexRgiht、IndexTab、IndexBottom,而Index组件只用于主页布局,具体渲染交给各个组件来完成

目录结构如下:

Index组件

import React, { useState, useEffect } from 'react';
import {Row, Col} from 'antd';
//新闻列表组件
import IndexTab from './IndexTab/index';
//主页左边内容 轮播图和新闻图文列表
import IndexLeft from './IndexLeft/index'
//主页底部新闻列表
import IndexBottom from './IndexBottom/index';
//主页右侧新闻列表
import IndexRight from './IndexRight/index';
import './index.scss';
const Index = props => {
    return (
        <div className='index'>
        <Row>
            <Col span={2}/>
            <Col span={21}>
                <Row className='top_news'>
                    <Col span={8}>
                        <div className='top_left top'>
                            <IndexLeft />
                        </div>
                    </Col>

                    <Col span={7}>
                        <div className='top_center top'>
                            <IndexTab />
                        </div>
                    </Col>

                    <Col span={6}>
                        <div className='top_right top'>
                            <IndexRight />
                        </div>
                    </Col>
                </Row>

                <Row>
                    <div className='bottom'>
                        <IndexBottom />     
                    </div>
                </Row>

            </Col>
            <Col span={4}/>
        </Row>
        </div>
    );
}
export default Index;

这里我通过ANT Design的栅格布局将页面划分成四个部分 每个部分引入对应的组件

布局方法这里不再叙述,详见https://ant.design/components/grid-cn/

2 until.js

在开始编码具体组件前,我们先顶一个until.js来放置我们的公共方法和变量

var Tools = {
    // fetch接口封装
    api: function({ url, args='', callback }) {
        let argsStr = '';
        if(args!='') {
            for(let key in args) {
                argsStr += key + '=' + args[key] + '&';
            }
            argsStr = '?' + argsStr.substr(0, argsStr.length-1);
        }
        
        fetch(url+argsStr)
        .then(response => response.json())
        .then(res => {
            callback(res);
        });
    },
    /**
     * zyx
     * 2019/10.22
     * 时间戳转时间‘YYYY-MM-DD HH:mm:ss’
     */
    time : function(time){
        //从数据库拿出来的时间戳是字符串形式 需要转化为数字
        time = parseInt(time);
        // 增加8小时
        let date = new Date(time + 8 * 3600 * 1000); 
        return date.toJSON().substr(0, 19).replace('T', ' ');
        //Date的‘toJSON’方法返回格林威治时间的JSON格式字符串,实际是使用‘toISOString’方法的结果。
        //字符串形如‘2018-08-09T10:20:54.396Z’,转化为北京时间需要额外增加八个时区,
        //我们需要取字符串前19位,然后把‘T’替换为空格,即是我们需要的时间格式。
    }
}

export const api = Tools.api.bind(Tools);
//host 请求地址
export const host = 'http://xxx.xx.xx.xxx/tp5/public/index.php/index/index/';
export const time = Tools.time.bind(Tools);

这里我们主要使用封装好的fetch请求和暴露出来的host

这里建议将host地址在这里暴露出来,这样当项目变动需要改请求地址时,我们只需要把这个地址改了就行,否则每个文件都需要改动

3 公共组件

这三个模块公用一个模块 ImgBlockTypeOne

这里三个模块的样式基本一样,不同点可能在排列方式和模块的width、height

所以渲染方式我使用flex布局

将justifyContent和这个模块的width、height以props的方式传递进去,来管控布局

然后传入type来分别渲染不同类别的新闻数据

import React, { useState, useEffect } from 'react';
import {Card} from 'antd';
import {Link} from 'react-router-dom';

//引入封装的fetch方法和host地址
import {api,host} from '../../until';
import './index.scss';

const ImgBlockTypeOne = props => {

     //定义需要渲染使用state
     const [news,setNews] = useState('');

     //组件加载时调用 相当于componentDidMount 
    useEffect(()=>{
        //动态获取数据 根据传入得type type不同拿到的数据类型不同
        let wenzhangType = props.type;
        /**
         * zyx
         * 2020/6/9
         * 拿到数据
         */
        api({
            url:host + 'newsSelectContentByType',
            args: {
                type:1,
                wenzhangType,
            },
            callback: (res) => {
                showData(res);
            }
        });    
    },[])

    /**
     * zyx
     * 2020/6/9
     * 数据处理函数 
     */
    const showData = (data)=>{
        let listData = [];
        for (let i = 0; i < props.count; i++) {
            let img = JSON.parse(data[i].img);
            listData.push({
                uniquekey: data[i].id,
                thumbnail_pic_s: img[0],
                title: data[i].title,
                author_name: data[i].name,
            });
        }
        //调用news的change方法
        setNews(listData)
    }
 
    //根据数据渲染方法
    //根据传入的componentType来渲染不同的样式
    //同时布局采用的flex布局 具体样式通过props传递 判断是横向的排列还是纵向排列
    const newsImage = ()=>{
        if(news.length){
            let newsList = '';
            if(props.componentType == 1){
                newsList=news.map((newsItem, index) => (
                    <div key={index} className='image_news_item' style={{width:props.imageWidth}}>
                        <Link to={`details/${newsItem.uniquekey}`} target='_blank'>
                            <img alt="newsItem.title" src={newsItem.thumbnail_pic_s} width={props.imageWidth}/>
                            <h3>{newsItem.title}</h3>
                            <p>{newsItem.author_name}</p>
                        </Link>
                    </div>
                ));

            }else if (props.componentType == 2){
                newsList=news.map((newsItem,index)=>(
                    <Link to={`details/${newsItem.uniquekey}`} target='_blank' key={index}>
                        <section  className='imageSingle_sec' style={{width:props.width}}>
                            <div className='imageSingle_left' style={{width:props.ImageWidth}}>
                                <img style={{width:props.ImageWidth}} src={newsItem.thumbnail_pic_s} alt={newsItem.title}/>
                            </div>
        
                            <div className='imageSingle_right'>
                                <p>{newsItem.title}</p>
                                <span className='realType' >{newsItem.realtype}</span>
                                <span>{newsItem.author_name}</span>
                            </div>
                        </section>
                    </Link>
                ));
            }
             
            return(
                <Card className={props.componentType == '2' ? 'imageSingleCard':'image_card'} title={props.cartTitle} bordered={true} style={{width:props.width,marginTop:'10px'}}>
                    <div className='image_news_container' style={{width:props.width,justifyContent:props.justifyContent}}>
                        {newsList}
                    </div>
                </Card>
            )
        }else{
            //判断数据是否为空 如果为空 直接渲染一个正在加载
            return '正在加载'
        }
    }
   
    return (
        <div className='pc_news_imgblock'>
          {/* 调用处理好的模块*/}
            {newsImage()}
        </div>
    )
}
export default ImgBlockTypeOne;

4 IndexLeft

主页左边布局的那一模块

使用 ANT Design 的轮播图 和公共组件ImgBlockTypeOne

import React, { useState, useEffect } from 'react';
import {Carousel} from 'antd';
import ImgBlockTypeOne from '../../component/ImgBlockTypeOne/index';
const IndexLeft = props => {
    
    return (
       <div>
           <Carousel autoplay>
                <div><img src="https://zyx-news.oss-cn-hangzhou.aliyuncs.com/news1.jpg"/></div>
                <div><img src="https://zyx-news.oss-cn-hangzhou.aliyuncs.com/news2.jpg"/></div>
                <div><img src="https://zyx-news.oss-cn-hangzhou.aliyuncs.com/news3.jpg"/></div>
                <div><img src="https://zyx-news.oss-cn-hangzhou.aliyuncs.com/news4.jpg"/></div>
            </Carousel>
            <ImgBlockTypeOne  count={10} type='4' width='100%' imageWidth='112px'
             cartTitle='美国大暴动' justifyContent='space-around' componentType='1'/>
        </div>
    )
}
export default IndexLeft;

5 IndexTab

主页中间布局的那一模块

使用ANT Design的Tab组件 来渲染不同的文章列表

import React, { useState, useEffect } from 'react';
import {Tabs} from 'antd';
import {Link} from 'react-router-dom';
import {host,api} from '../../until';

const { TabPane } = Tabs;

const IndexTab = props => {
    const callback = (key)=>{
        console.log(key);
    }

    //定义渲染需要使用的数据
    const [news,setNews] =useState([]);
     //组件加载时调用 相当于componsetNewsentDidMount 
     useEffect(()=>{
        //动态获取数据 根据传入得type
        let wenzhangType = props.type;
        /**
         * zyx
         * 2020/6/19
         * 拿到数据
         */
        api({
            url:host + 'newsSelectContentByType',
            args: {
                type:1,
            },
            callback: (res) => {
                showData(res);
            }
        });    
    },[])

    /**
     * zyx
     * 2020/6/9
     * 数据处理函数
     */
    const showData = (data)=>{
        let listData = [];
        for (let i = 0; i < data.length; i++) {
            let img = JSON.parse(data[i].img);
            listData.push({
                uniquekey: data[i].id,
                title: data[i].title,
            });
        }
        //调用news的change方法
        setNews(listData)
    }

    const List = ()=>{
        // 将对应的新闻数据渲染成li列表
        let newsList=news.map((newsItem, index) => (
            <li key={index}>
                 <Link to={`details/${newsItem.uniquekey}`} target='_blank'>
                     {newsItem.title}
                 </Link>
             </li>
        ));
        return(
            <ul>
                {newsList}
            </ul>
        )
    }
    return (
       <div>
            <Tabs defaultActiveKey="1" onChange={callback}>
                <TabPane tab="热点文章" key="1">
                    <List />
                </TabPane>
                <TabPane tab="热点帖子" key="2">
                    <List />
                </TabPane>
                <TabPane tab="国际新闻" key="3">
                    <List />
                </TabPane>
                <TabPane tab="国内新闻" key="4">
                    <List />
                </TabPane>
            </Tabs>
        </div>
    )
}
export default IndexTab;

6 IndexRight

主页右边布局那一个模块,纯展示模块 直接调用公共模块ImgBlockTypeOne

import React, { useState, useEffect } from 'react';
import ImgBlockTypeOne from '../../component/ImgBlockTypeOne/index';
const IndexRight = props => {
    
    return (
       <div>
            <ImgBlockTypeOne width='100%' ImageWidth='100px' type='3' count={5} cartTitle='海贼王' componentType='2'/>
        </div>
    )
}
export default IndexRight;

7IndexBottom

主页底边布局那一个模块,纯展示模块 直接调用公共模块ImgBlockTypeOne

import React, { useState, useEffect } from 'react';
import ImgBlockTypeOne from '../../component/ImgBlockTypeOne/index';
const IndexBottom = props => {
    
    return (
       <div>
            <ImgBlockTypeOne count={12} type='2' width='100%' imageWidth='112px' cartTitle='R.I.P Kobe'
            justifyContent='space-start' componentType='1'/>
        </div>
    )
}
export default IndexBottom;

一个主页抽离这么多模块主要是为了将数据处理和页面渲染分开,降低项目耦合


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK