likes
comments
collection
share

React Social Media App 静态页面部分

作者站长头像
站长
· 阅读数 33

React Social Media App 静态页面部分

使用技术:

React scss react-router

原视频地址

www.youtube.com/watch?v=Fwe…

这个项目是一个全栈项目,也是我的第一个 react 项目,以下为项目的静态页面部分的构建流程

初始化工作

下载文件


克隆一个初始仓库 github.com/safak/youtu…

git clone --single-branch -b react-mini https://github.com/safak/youtube2022.git

指定 react-mini 分支

再下载依赖 npm i

下载 sass 命令: npm i sass

构建文件结构


src 下创建 pages 文件夹

然后分别创建 home login profile register 文件夹

各文件下创建 .jsx 文件 和 scss 文件

React Social Media App 静态页面部分

App.js 文件中加入 Login 组件

import Login from './pages/login/login'

function App() {
  return (
    <div>
      <Login />
    </div>
  );
}

export default App;

搭建

登录界面

登录界面的最终呈现效果是这样的

React Social Media App 静态页面部分 HTML 结构


因为使用了 scss,它可以做到直接在父类的样式中递进地写子类的样式,所以 HTML 结构中类的命名较少

  • 基础的 HTML 结构

    将整个盒子称作 card ,分为 leftright 两个部分

    <div className='login-page'>
                <div className='card'>
                    <div className='left'>
                        <h1>Hello World.</h1>
                        <p>
                            Lorem ipsum dolor sit amet consectetur adipisicing elit.Libero cum,
                            alias totam numquam ipsa exercitationem dignissimos,error nam,
                            consequatur.
                        </p>
                        <span>Don't you have an account?</span>
                        <button>Register</button>
                    </div>
                    <div className="right">
                        <h1>Login</h1>
                        <form>
                            <input type="text" placeholder='Username' />
                            <input type="password" placeholder="Password" />
                            <button>Login</button>
                        </form>
                    </div>
                </div>
            </div>
    

样式


根据HTML结构直接编写样式就行了

  • 样式

    .login {
        height: 100vh;
        background-color: rgb(193, 190, 255);
        display: flex;
        justify-content: center;
        align-items: center;
    
        .card {
            width: 50%;
            min-height: 600px;
            display: flex;
            background-color: #fff;
            border-radius: 10px;
            overflow: hidden;
    
            .left {
                display: flex;
                flex: 1;
                flex-direction: column;
                gap: 30px;
                background: linear-gradient(rgba(39, 11, 96, .5), rgba(39, 11, 96, .5)), url("https://i.328888.xyz/2023/03/20/PsE9H.jpeg") center;
                background-size: cover;
                padding: 50px;
                color: white;
    
                h1 {
                    font-size: 100px;
                    line-height: 100px;
                }
    
                p {}
    
                span {
                    font-size: 14px;
                }
    
                button {
                    width: 50%;
                    padding: 10px;
                    border: none;
                    border-color: #fff;
                    color: rebeccapurple;
                    font-weight: bold;
                    cursor: pointer;
                }
            }
    
            .right {
                display: flex;
                justify-content: center;
                flex: 1;
                padding: 50px;
                flex-direction: column;
                gap: 50px;
    
                h1 {
                    color: #555;
                }
    
                form {
                    display: flex;
                    flex-direction: column;
                    gap: 30px;
    
                    input {
                        border: none;
                        border-bottom: 1px solid lightgray;
                        padding: 20px 10px;
                    }
    
                    button {
                        width: 50%;
                        padding: 10px;
                        border: none;
                        background-color: #938eef;
                        color: white;
                        font-weight: bold;
                        cursor: pointer;
                    }
                }
    
            }
        }
    }
    

这其中有个细节,就是给 card 设置圆角,之后又给 left 设置背景图片,图片溢出挡住了圆角

解决方案是 overflow: hidden;

React Social Media App 静态页面部分

注册

React Social Media App 静态页面部分

HTML结构就相对于登录界面修改一点

  • HTML结构

    import './register.scss'
    
    export default function Register() {
        return (
            <div className='register'>
                <div className='card'>
                    <div className='left'>
                        <h1>Hello World.</h1>
                        <p>
                            Lorem ipsum dolor sit amet consectetur adipisicing elit.Libero cum,
                            alias totam numquam ipsa exercitationem dignissimos,error nam,
                            consequatur.
                        </p>
                        <span>Do you have an account?</span>
    										<button>Login</button>
                    </div>
                    <div className="right">
                        <h1>Register</h1>
                        <form>
                            <input type="text" placeholder='Username' />
                            <input type="email" placeholder='Email' />
                            <input type="password" placeholder="Password" />
                            <input type="text" placeholder='Name' />
                            <button>Register</button>
                        </form>
                    </div>
                </div>
            </div>
        )
    }
    

样式就是修改一下背景图片,以及给 card 添加 flex-direction: row-reverse; 直接将左右转换

  • 样式

    .register {
        height: 100vh;
        background-color: rgb(193, 190, 255);
        display: flex;
        justify-content: center;
        align-items: center;
    
        .card {
            display: flex;
            flex-direction: row-reverse;
            width: 50%;
            min-height: 600px;
            background-color: #fff;
            border-radius: 10px;
            overflow: hidden;
    
            .left {
                display: flex;
                flex: 1;
                flex-direction: column;
                gap: 30px;
                background: linear-gradient(rgba(39, 11, 96, .5), rgba(39, 11, 96, .5)), url("https://i.328888.xyz/2023/03/20/P9BtZ.jpeg") center;
                background-size: cover;
                padding: 50px;
                color: white;
    
                h1 {
                    font-size: 100px;
                    line-height: 100px;
                }
    
                p {}
    
                span {
                    font-size: 14px;
                }
    
                button {
                    width: 50%;
                    padding: 10px;
                    border: none;
                    border-color: #fff;
                    color: rebeccapurple;
                    font-weight: bold;
                    cursor: pointer;
                }
            }
    
            .right {
                display: flex;
                justify-content: center;
                flex: 1;
                padding: 50px;
                flex-direction: column;
                gap: 50px;
    
                h1 {
                    color: #555;
                }
    
                form {
                    display: flex;
                    flex-direction: column;
                    gap: 30px;
    
                    input {
                        border: none;
                        border-bottom: 1px solid lightgray;
                        padding: 20px 10px;
                    }
    
                    button {
                        width: 50%;
                        padding: 10px;
                        border: none;
                        background-color: #938eef;
                        color: white;
                        font-weight: bold;
                        cursor: pointer;
                    }
                }
    
            }
        }
    }
    

使用 react-router

登录页面与注册页面相互切换

npm i react-router-dom 安装依赖

在 APP.js 中配置路由

import Login from './pages/login/login'
import Register from './pages/register/register'
import {
  createBrowserRouter,
  RouterProvider,
} from "react-router-dom";

const router = createBrowserRouter([
  {
    path: "/login",
    element: <Login />
  },
  {
    path: "/register",
    element: <Register />
  }
]);

function App() {
  return (
    <div>
      <RouterProvider router={router} />
    </div>
  );
}

export default App;

Login.jsx and Register.jsx 中导入 Link import {Link} from 'react-router-dom’

// Login.jsx
<Link to='/register'>
     <button>Register</button>
</Link>

// Register.jsx
<Link to='/login'>
    <button>Login</button>
</Link>

实现首页

初始化

首先构建新的文件结构

src 下添加 components

接着添加 leftBar rightBar navBar

React Social Media App 静态页面部分

构造主页的 HTML结构

const Layout = () => {
    return (
      <div>
        <NavBar />
        <div style={{ display: 'flex' }}>
          <LeftBar />
          <Outlet />
          <RightBar />
        </div>
      </div>
    )
  }

更改路由配置

const router = createBrowserRouter([
    **{
      path: "/",
      element: <Layout />,
      children: [
        {
          path: '/',
          element: <Home/>
        },
        {
          path: '/profile/:id',
          element: <Profile/>
        }
      ]
    },**
    {
      path: "/login",
      element: <Login />
    },
    {
      path: "/register",
      element: <Register />
    }
  ]);

重定向


当未登录时,应跳转到登录页面,而非首页

当然这里的判断是否登录需要与后端合作,现阶段仅是实现静态页面,所以下面只是展示重定向策略

创建一个函数判断是否登录,已登录就返回主页,未登录就重定向到登录页面

function App() {
  **const currentUser = false;**

  **const ProtectRoute = ({ children }) => {
    if (!currentUser) {
      return <Navigate to='/login' />
    }

    return children
  }**

  const router = createBrowserRouter([
    {
      path: "/",
      **element: <ProtectRoute>
        <Layout />
      </ProtectRoute>,**
 //..............................
}

navBar

我使用的是阿里巴巴矢量图标库的 icon 单个使用法

故在 navBar.scss 文件中引入 icon (链接必然会失效,仅展示)

@font-face {
    font-family: 'iconfont';
    /* Project id 3591478 */
    src: url('//at.alicdn.com/t/c/font_3591478_6mxgs3fjb3h.woff2?t=1679327226324') format('woff2'),
        url('//at.alicdn.com/t/c/font_3591478_6mxgs3fjb3h.woff?t=1679327226324') format('woff'),
        url('//at.alicdn.com/t/c/font_3591478_6mxgs3fjb3h.ttf?t=1679327226324') format('truetype');
}

.iconfont {
    font-family: "iconfont" !important;
    font-size: 16px;
    font-style: normal;
    -webkit-font-smoothing: antialiased;
    -webkit-text-stroke-width: 0.2px;
    -moz-osx-font-smoothing: grayscale;
}
  • HTML 结构

    import './navBar.scss';
    import {Link} from 'react-router-dom'
    
    export default function NavBar() {
        return (
            <div className='nav-bar'>
                <div className="left">
                    <Link to='/'>
                        <span>lamasocial</span>
                    </Link>
                    <i className='iconfont home'>&#xe6b8;</i>
                    <i className="iconfont moon">&#xe625;</i>
                    <i className="iconfont function">&#xe84a;</i>
                    <div className="search">
                        <i className="iconfont search-icon">&#xe651;</i>
                        <input type="text" placeholder='search....' />
                    </div>
                </div>
                <div className="right">
                <i className="iconfont profile">&#xe623;</i>
                <i className="iconfont email">&#xe61c;</i>
                <i className="iconfont remind">&#xe627;</i>
                <div className="user">
                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-g7oj73.jpg"/>
                    <span>John Doe</span>
                </div>
                </div>
            </div>
        )
    }
    

navBar 分为左右部分,整体都使用弹性分布,左右在主轴 space-between 分布

这里使用了 object-fit 属性(新知识)

object-fit  CSS属性指定可替换元素(例如:<img>或 <video>)的内容应该如何适应到其使用高度和宽度确定的框。

navBar 还需要固定在顶部

.navBar {		
		**background-color: white;
    position: sticky;
    top: 0;
}**
  • 样式

    /* CDN 服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */
    /* CDN 服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */
    /* CDN 服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */
    /* CDN 服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */
    @font-face {
        font-family: 'iconfont';
        /* Project id 3591478 */
        src: url('//at.alicdn.com/t/c/font_3591478_q40gy84nnm.woff2?t=1679365108671') format('woff2'),
            url('//at.alicdn.com/t/c/font_3591478_q40gy84nnm.woff?t=1679365108671') format('woff'),
            url('//at.alicdn.com/t/c/font_3591478_q40gy84nnm.ttf?t=1679365108671') format('truetype');
    }
    
    .iconfont {
        font-family: "iconfont" !important;
        font-size: 16px;
        font-style: normal;
        -webkit-font-smoothing: antialiased;
        -webkit-text-stroke-width: 0.2px;
        -moz-osx-font-smoothing: grayscale;
    }
    
    .nav-bar {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 10px 20px;
        height: 50px;
        border-bottom: 1px solid lightgray;
    		background-color: white;
        position: sticky;
        top: 0;
    
        .left {
            display: flex;
            align-items: center;
    
            gap: 30px;
    
            span {
                font-weight: bold;
                font-size: 20px;
                color: darkblue;
            }
    
            .search {
                display: flex;
                align-items: center;
                gap: 10px;
                border: 1px solid lightgray;
                border-radius: 5px;
                padding: 5px;
    
                input {
                    border: none;
                    width: 500px;
                }
            }
        }
    
        .right {
            display: flex;
            align-items: center;
            gap: 20px;
    
            .user {
                display: flex;
                align-items: center;
                gap: 10px;
                font-weight: 500;
    
                img {
                    width: 30px;
                    height: 30px;
                    border-radius: 50%;
                    object-fit: cover;
                }
    
                span {}
            }
        }
    }
    

leftBar

这个组件需要许多作为图标的小图片,所以在 src 下创建 assets/imgs 文件夹保存小图片资源

这个组件结构的想法是,用一个 container 包裹整个,其中有三个 menu,每一项就是 item

当然有个特殊的就是第一个 menu 有个 user

React Social Media App 静态页面部分

图片路径使用的是引入保存在变量中

  • HTML

    import './leftBar.scss';
    import Friends from '../../assets/imgs/泡泡.png';
    import Groups from '../../assets/imgs/蛋糕.png';
    import Gallery from '../../assets/imgs/相机.png';
    import Watch from '../../assets/imgs/眼睛.png';
    import Memories from '../../assets/imgs/闹钟.png';
    import Market from '../../assets/imgs/购物.png';
    import Events from '../../assets/imgs/风筝.png';
    import Gaming from '../../assets/imgs/纸飞机.png';
    import Video from '../../assets/imgs/礼花.png';
    import Message from '../../assets/imgs/对话框.png';
    import Fundraiser from '../../assets/imgs/花.png';
    import Tutorials from '../../assets/imgs/彩铅.png';
    import Courses from '../../assets/imgs/煎蛋.png';
    
    export default function LeftBar() {
        return (
            <div className='left-bar'>
                <div className="container">
                    <div className="menu">
                        <div className="user">
                        <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-g7oj73.jpg"/>
                            <span>John Doe</span>
                        </div>
                        <div className="item">
                            <img src={Friends}/>
                            <span>Friends</span>
                        </div>
                        <div className="item">
                            <img src={Groups}/>
                            <span>Groups</span>
                        </div>
                        <div className="item">
                            <img src={Market}/>
                            <span>Market</span>
                        </div>
                        <div className="item">
                            <img src={Watch}/>
                            <span>Watch</span>
                        </div>
                        <div className="item">
                            <img src={Memories}/>
                            <span>Memories</span>
                        </div>
                        
                    </div>
                    <hr/>
                    <div className="menu">
                        <span>Your shortcuts</span>
                        <div className="item">
                            <img src={Events}/>
                            <span>Events</span>
                        </div>
                        <div className="item">
                            <img src={Gaming}/>
                            <span>Gaming</span>
                        </div>
                        <div className="item">
                            <img src={Gallery}/>
                            <span>Gallery</span>
                        </div>
                        <div className="item">
                            <img src={Video}/>
                            <span>Video</span>
                        </div>
                        <div className="item">
                            <img src={Message}/>
                            <span>Message</span>
                        </div>
                    </div>
                    <hr/>
                    <div className="menu">
                        <span>Others</span>
                        <div className="item">
                            <img src={Fundraiser}/>
                            <span>Fundraiser</span>
                        </div>
                        <div className="item">
                            <img src={Tutorials}/>
                            <span>Tutorials</span>
                        </div>
                        <div className="item">
                            <img src={Courses}/>
                            <span>Courses</span>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
    

依然使用弹性布局,container 主轴方向改为竖轴

整个主页部分打算的也是弹性布局,而 leftBarflex: 2

leftBar 依然需要固定在左侧,但是 leftBar 的内容有点多,很可能溢出,但是它被固定了,就不会随着主页的滚动而显示溢出的部分

这时就使用 overflow: scroll; 即可,取消滚动条 &::-webkit-scrollbar {display: none; }

(因为这对 rightBar 也生效,所以直接复制粘贴到 rightBar.scss 中)

  • 样式

    img {
        width: 30px;
    }
    
    .left-bar {
        **flex: 2;**
        position: sticky;
        top: 50px;
        height: calc(100vh - 70px);
        overflow: scroll;
    
        &::-webkit-scrollbar {
            display: none;
        }
    
        .container {
            padding: 20px;
    
            hr {
                margin: 20px 0;
                border: none;
                height: .5px;
                background-color: lightgray;
            }
    
            .menu {
                display: flex;
                flex-direction: column;
                gap: 20px;
    
                span {
                    font-size: 12px;
                }
    
                .user {
                    display: flex;
                    align-items: center;
                    gap: 10px;
    
                    img {
                        height: 30px;
                        border-radius: 50%;
                        object-fit: cover;
                    }
    
                    span {
                        font-size: 14px;
                    }
                }
    
                .item {
                    display: flex;
                    align-items: center;
                    gap: 10px;
    
                    img {}
    
                    span {
                        font-size: 14px;
                    }
                }
            }
        }
    }
    

rightBar

React Social Media App 静态页面部分

rightBar 有三个 itemitem 中内容都与 user 有关

  • HTML 结构

    import './rightBar.scss';
    
    export default function RightBar() {
        return (
            <div className='right-bar'>
                <div className="container">
                    <div className="item">
                        <span>Suggestions for you</span>
                        <div className="user">
                            <div className="user-info">
                            <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <span>Jane Doe</span>
                            </div>
                            <div className="buttons">
                                <button>follow</button>
                                <button>dismiss</button>
                            </div>
                        </div>
                        <div className="user">
                            <div className="user-info">
                            <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <span>Jane Doe</span>
                            </div>
                            <div className="buttons">
                                <button>follow</button>
                                <button>dismiss</button>
                            </div>
                        </div>
                    </div>
                    <div className="item">
                        <span>Latest Activities</span>
                        <div className="user">
                            <div className="user-info">
                                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <p>
                                    <span>Jane Doe</span> changed their cover picture
                                </p>
                            </div>
                            <span>1 min ago</span>
                        </div>
                        <div className="user">
                            <div className="user-info">
                                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <p>
                                    <span>Jane Doe</span> changed their cover picture
                                </p>
                            </div>
                            <span>1 min ago</span>
                        </div>
                        <div className="user">
                            <div className="user-info">
                                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <p>
                                    <span>Jane Doe</span> changed their cover picture
                                </p>
                            </div>
                            <span>1 min ago</span>
                        </div>
                        <div className="user">
                            <div className="user-info">
                                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <p>
                                    <span>Jane Doe</span> changed their cover picture
                                </p>
                            </div>
                            <span>1 min ago</span>
                        </div>
                    </div>
                    <div className="item">
                        <span>Online Friends</span>
                        <div className="user">
                            <div className="user-info">
                                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <div className="online"></div>
                                <span>Jane Doe</span>
                            </div>
                        </div>
                        <div className="user">
                            <div className="user-info">
                                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <div className="online"></div>
                                <span>Jane Doe</span>
                            </div>
                        </div>
                        <div className="user">
                            <div className="user-info">
                                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <div className="online"></div>
                                <span>Jane Doe</span>
                            </div>
                        </div>
                        <div className="user">
                            <div className="user-info">
                                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <div className="online"></div>
                                <span>Jane Doe</span>
                            </div>
                        </div>
                        <div className="user">
                            <div className="user-info">
                                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <div className="online"></div>
                                <span>Jane Doe</span>
                            </div>
                        </div>
                        <div className="user">
                            <div className="user-info">
                                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-1k97z3.png"/>
                                <div className="online"></div>
                                <span>Jane Doe</span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
    

盒子阴影使用的是 www.cssmatic.com/box-shadow 网站快捷生成的

需要注意的是在线好友 item 中头像上的绿点是定位实现的

  • 样式

    .right-bar {
        flex: 3;
        position: sticky;
        top: 50px;
        height: calc(100vh - 70px);
        overflow: scroll;
    
        &::-webkit-scrollbar {
            display: none;
        }
    
        .container {
            padding: 20px;
    
            .item {
                -webkit-box-shadow: -1px 5px 19px 0px rgba(194, 188, 194, 0.31);
                -moz-box-shadow: -1px 5px 19px 0px rgba(194, 188, 194, 0.31);
                box-shadow: -1px 5px 19px 0px rgba(194, 188, 194, 0.31);
                padding: 20px;
                margin-bottom: 20px;
    
                span {
                    color: gray;
                }
    
                .user {
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    margin: 20px 0;
    
                    .user-info {
                        display: flex;
                        align-items: center;
                        gap: 20px;
                        position: relative;
    
                        img {
                            height: 40px;
                            width: 40px;
                            border-radius: 50%;
                            object-fit: cover;
                        }
    
                        .online {
                            height: 12px;
                            width: 12px;
                            border-radius: 50%;
                            background-color: limegreen;
                            position: absolute;
                            top: 0;
                            left: 30px;
                        }
    
                        p {
                            color: gray;
                        }
    
                        span {
                            font-weight: 500;
                            color: #000;
                        }
                    }
    
                    .buttons {
                        display: flex;
                        align-items: center;
                        gap: 10px;
    
                        button {
                            border: none;
                            padding: 5px;
                            color: white;
                            cursor: pointer;
    
                            &:first-child {
                                background-color: #5271ff;
                            }
    
                            &:last-child {
                                background-color: #f0544f;
                            }
                        }
                    }
                }
            }
        }
    }
    

设置光亮与夜间模式

设置光亮与夜间模式总是意味着要写两套样式,但是如果使用 scss 的函数,则只需要写两套关于颜色的变量

style.scss 中设置变量与函数

$themes: (
    light: (textColor: #000,
        bg: #fff,
        logo: darkblue,
        bgSoft: #f6f3f3,
        textColorSoft: #555,
        border: lightgray),
    dark: (textColor: whitesmoke,
        bg: #222,
        logo: white,
        bgSoft: #333,
        textColorSoft: lightgray,
        border: #444)
);

@mixin themify($themes) {

    @each $theme,
    $map in $themes {
        .theme-#{$theme} & {
            $theme-map: () !global;

            @each $key,
            $submap in $map {
                $value: map-get(map-get($themes, $theme), "#{$key}");
                $theme-map: map-merge($theme-map,
                    ($key: $value,
                    )) !global;
            }

            @content;
            $theme-map: null !global;
        }
    }
}

@function themed($key) {
    @return map-get($theme-map, $key);
}

a {
    text-decoration: none;
}

然后分别在 navBar leftBar rightBar 三个组件的 scss 文件中使用函数即可

navBar 为例,凡是需要更改颜色的地方就使用函数传入对应参数名称

@import "../../style.scss";

.nav-bar {
	@include themify($themes) {
		//..........
		border-bottom: 1px solid themed("border");
    background-color: themed("bg");
    color: themed('textColor');
		
		.left {
			//......
			.search {
				//..............
				border: 1px solid themed("border");
				
				input {
					color: themed('textColor');
				}
			}
		}
	}
}

脚本部分


但现在仅仅只是样式设置好了,脚本还没动呢

首先我们会想到,与改变模式有关的值其实就是,navBar 的一个 icon,那么我们应该在 navBar 组件中使用 state 吗,显然不是,因为这个 state 还会影响 leftBar rightBar 以及主页的 outlet,其实就是会影响整个主页。

那我们应该把在 App.jsx 中使用 state 吗,如果在 App.jsx 中使用 state ,而 navBar 需要有改变 state 的权力,那就会使用 props ,那如果还有其他子组件也需要呢,这会比较麻烦

所以使用 context ,由 context 保管 state,然后 context 包裹 App.jsx 这样 App.jsx 和它的子组件就可以通过使用 useContext 来获取 state

src 下创建 context/darkModeContet.js

暴露出 darkMode 和可以改变它的函数

  • 脚本

    import { useState, createContext, useEffect } from 'react';
    
    export const DarkModeContext = createContext();
    
    export function DarkModeContextProvider({ children }) {
        const [darkMode, setDarkMod] = useState(JSON.parse(localStorage.getItem('darkMode')) || false);
    
        const toggle = () => {
            setDarkMod(!darkMode);
        }
    
        useEffect(() => {
            localStorage.setItem('darkMode', darkMode)
        }, [darkMode]);
    
        return (
            <DarkModeContext.Provider value={{ darkMode, toggle }} >
                {children}
            </ DarkModeContext.Provider>
        )
    }
    
    // index.js
    // ..............................
    root.render(
      <React.StrictMode>
        <DarkModeContextProvider>
          <App />
        </DarkModeContextProvider>
      </React.StrictMode>
    );
    
    // App.jsx
    // .....................
    function App() {
      const currentUser = true;
      **const { darkMode } = useContext(DarkModeContext);**
    
      const Layout = () => {
        return (
          **<div className={`theme-${darkMode ? 'dark' : 'light'}`}>**
            <NavBar />
            <div style={{ display: 'flex' }}>
              <LeftBar />
              <div style={{ flex: 6 }}>
                <Outlet />
              </div>
              <RightBar />
            </div>
          </div>
        )
      }
    
    //................................
    }
    
    // navBar.jsx
    
    export default function NavBar() {
        **const {darkMode,toggle} = useContext(DarkModeContext);**
    
        return (
            <div className='nav-bar'>
                <div className="left">
                    <Link to='/'>
                        <span>lamasocial</span>
                    </Link>
                    <i className='iconfont home-icon'>&#xe6b8;</i>
                    **{darkMode ? <i className="iconfont moon" onClick={toggle}>&#xe635;</i> 
    								: <i className="iconfont moon" onClick={toggle}>&#xe625;</i>}**
                    <i className="iconfont function">&#xe84a;</i>
                    <div className="search">
                        <i className="iconfont search-icon">&#xe651;</i>
                        <input type="text" placeholder='search....' />
                    </div>
                </div>
                <div className="right">
                <i className="iconfont profile">&#xe623;</i>
                <i className="iconfont email">&#xe61c;</i>
                <i className="iconfont remind">&#xe627;</i>
                <div className="user">
                <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-g7oj73.jpg"/>
                    <span>John Doe</span>
                </div>
                </div>
            </div>
        )
    }
    

初步实现检查是否登录

首先是像创建 darkModeContext 一样创建 authContext

user 的数据暂时固定

import { useState, createContext, useEffect } from 'react';

export const AuthContext = createContext();

export function AuthContextProvider({ children }) {
    const [currentUser, setCurrentUser] = useState(JSON.parse(localStorage.getItem('user')) || null);

    const login = () => {
        // TO DO
        setCurrentUser({
            id: 1,
            name: 'John Doe',
            profilePic: 'https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-g7oj73.jpg'
        })
    }

    useEffect(() => {
        localStorage.setItem('user', JSON.stringify(currentUser))
    }, [currentUser]);

    return (
        <AuthContext.Provider value={{ currentUser, login }} >
            {children}
        </ AuthContext.Provider>
    )
}

更改 login.jsx 让其的 login 按钮触发 AuthContextlogin 方法

再修改凡是与 currentUser 有关的组件,这些组件使用 currentUer 的数据

stories

React Social Media App 静态页面部分

  • HTML结构

    import './stories.scss';
    import {useContext} from 'react';
    import { AuthContext } from '../../context/authContext';
    
    export default function Stories() {
        const {currentUser} = useContext(AuthContext);
        
        const stories = [
            {
                id:1,
                name: 'klns',
                img: 'https://i.328888.xyz/2023/03/22/YvOPv.jpeg'
            },
            {
                id:2,
                name: 'llxs',
                img: 'https://i.328888.xyz/2023/03/22/YvHEU.jpeg'
            },
            {
                id:3,
                name: 'xlys',
                img: 'https://i.328888.xyz/2023/03/22/Yv22p.jpeg'
            },
            {
                id:4,
                name: 'ylrs',
                img: 'https://i.328888.xyz/2023/03/22/YvlqL.jpeg'
            }
        ]
    
        return (
            <div className="stories">
                <div className="story">
                        <img src={currentUser.profilePic} alt="" />
                        <span>{currentUser.name}</span>
                        <button>+</button>
                    </div>
                {stories.map(story => (
                    <div className="story">
                        <img src={story.img} alt="" />
                        <span>{story.name}</span>
                    </div>
                ))}
            </div>
        )
    }
    
  • 样式

    .stories {
        display: flex;
        height: 250px;
        margin-bottom: 30px;
        gap: 10px;
    
        .story {
            flex: 1;
            border-radius: 10px;
            overflow: hidden;
            position: relative;
    
            img {
                width: 100%;
                height: 100%;
                object-fit: cover;
            }
    
            span {
                position: absolute;
                bottom: 10px;
                left: 10px;
                color: white;
                font-weight: 500;
            }
    
            button {
                display: flex;
                justify-content: center;
                align-items: center;
                width: 30px;
                height: 30px;
                position: absolute;
                bottom: 40px;
                left: 10px;
                color: #fff;
                background-color: #5271ff;
                border: none;
                border-radius: 50%;
                cursor: pointer;
                font-size: 30px;
            }
        }
    }
    

posts

组件结构


posts 是一个父组件,子组件为 post

React Social Media App 静态页面部分

React Social Media App 静态页面部分

post 组件中还有子组件 comments,并且评论的打开关闭应该交给 post 的一个 state

React Social Media App 静态页面部分

代码内容


暂时用假数据

  • posts 的 jsx

    import './posts.scss'
    import Post from '../post/post';
    
    const posts = [
        {
            id: 1,
            name: 'klns',
            useId: 1,
            profilePic: 'https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-3zdpkd.jpg',
            desc: 'Lorem ipsum dolor sit amet consectetur adipisicing elit',
            img: 'https://i.328888.xyz/2023/03/22/Y9GUy.jpeg'
        },
        {
            id: 2,
            name: 'jane crimon',
            useId: 2,
            profilePic: 'https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-x8v5mv.jpg',
            desc: 'Tenetur iste voluptates dolorem rem commodi voluptate pariatur,voluptatum',
            img: 'https://i.328888.xyz/2023/03/22/Y96K3.jpeg'
        }
    ]
    
    export default function Posts() {
        return (
            <div className="posts">
                {posts.map(post => (
                    <Post post={post} key={post.id}/>
                ))}
            </div>
        )
    }
    
  • posts 的样式

    .posts {
        display: flex;
        flex-direction: column;
        gap: 50px;
    }
    

每个 post 组件大致分为四个部分,用户信息,内容,一些操作,以及评论组件

每个 post 组件都需要相关的数据,而这些数据暂时由 posts 组件传入它的假数据

React Social Media App 静态页面部分

React Social Media App 静态页面部分

  • post 的 jsx

    import {Link} from 'react-router-dom';
    import {useState} from 'react';
    import Comments from '../comments/comments'
    import './post.scss'
    
    export default function Post({post}) {
        const [openComment, setOpenComment] = useState(false);
        
        // temporary
        let liked = true;
    
        return (
            <div className="post">
                <div className="container">
                <div className="user">
                    <div className="user-info">
                        <img src={post.profilePic}/>
                        <div className="details">
                            <Link to={`/profile/${post.useId}`}>
                                <span className='name'>{post.name}</span>
                            </Link>
                            <span className='date'>1 min ago</span>
                        </div>
                    </div>
                    <i className='iconfont more'>&#xe719;</i>
                </div>
                <div className="content">
                    <p>{post.desc}</p>
                    <img src={post.img} alt="" />
                </div>
                <div className="info">
                    <div className="item">
                        {liked ? <i className='iconfont like' style={{color:'red'}}>&#xe8c3;</i> : <i className='iconfont like'>&#xeca1;</i>}
                        12 Likes
                    </div>
                    <div className="item" onClick={() => {setOpenComment(!openComment)}}>
                        <i className="iconfont comments">&#xe6ad;</i>
                        12 Comments
                    </div>
                    <div className="item">
                        <i className="iconfont share">&#xe739;</i>
                        Share   
                    </div>
                </div>
                {openComment && <Comments/>}
                </div>
            </div>
        )
    }
    
  • post 的样式

    @import '../../style.scss';
    
    @font-face {
        font-family: 'iconfont';
        /* Project id 3591478 */
        src: url('//at.alicdn.com/t/c/font_3591478_895afjog48r.woff2?t=1679493172073') format('woff2'),
            url('//at.alicdn.com/t/c/font_3591478_895afjog48r.woff?t=1679493172073') format('woff'),
            url('//at.alicdn.com/t/c/font_3591478_895afjog48r.ttf?t=1679493172073') format('truetype');
    }
    
    .iconfont {
        font-family: "iconfont" !important;
        font-size: 16px;
        font-style: normal;
        -webkit-font-smoothing: antialiased;
        -webkit-text-stroke-width: 0.2px;
        -moz-osx-font-smoothing: grayscale;
    }
    
    .post {
        @include themify($themes) {
            -webkit-box-shadow: -14px 8px 55px -27px rgba(0, 0, 0, 0.37);
            -moz-box-shadow: -14px 8px 55px -27px rgba(0, 0, 0, 0.37);
            box-shadow: -14px 8px 55px -27px rgba(0, 0, 0, 0.37);
            border-radius: 20px;
            background-color: themed("bg");
            color: themed("textColor");
    
            a {
                color: inherit;
            }
    
            .container {
                padding: 20px;
    
                .user {
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
    
                    .user-info {
                        display: flex;
                        gap: 20px;
    
                        img {
                            width: 40px;
                            height: 40px;
                            border-radius: 50%;
                            object-fit: cover;
                        }
    
                        .details {
                            display: flex;
                            flex-direction: column;
    
                            .name {
                                font-weight: 500;
                            }
    
                            .date {
                                font-size: 12px;
                            }
                        }
                    }
    
                    .more {
                        width: 20px;
                        height: 20px;
                        font-size: 20px;
                    }
                }
    
                .content {
                    margin: 20px 0;
    
                    img {
                        width: 100%;
                        max-height: 500px;
                        object-fit: cover;
                        margin-top: 20px;
                    }
                }
    
                .info {
                    display: flex;
                    align-items: center;
                    gap: 20px;
    
                    .item {
                        display: flex;
                        align-items: center;
                        gap: 10px;
                        cursor: pointer;
                        font-size: 14px;
                    }
                }
            }
        }
    }
    

评论组件需要获得当前用户的信息,所以需要使用 useContext 拿到 AuthContext 的内容

  • comments 的 jsx

    import './comments.scss';
    import { useContext } from 'react';
    import { AuthContext } from '../../context/authContext';
    
    const comments = [
        {
            id: 1,
            desc: `Beautiful project.. thanks once more Lama, you're an amazing teacher. So about the backend , using Sql, I got to try "Prisma", and it's a very powerful ORM to work with when working with SQL, and it does 
            work with MongoDb as well. It would be nice to see it used on this project for the backend. Thanks once more'`,
            name: 'Jane Doe',
            userId: 3,
            profilePic: 'https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-dplz6l.jpg'
        },
        {
            id:2,
            desc: `Amazing project as always! You're the best teacher on the YouTube for JavaScript! Appreciated your great efforts`,
            name: 'Jane Doe',
            userId: 4,
            profilePic: 'https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-y8zek7.png'
        }
    ]
    
    export default function Comments() {
        const {currentUser} = useContext(AuthContext);
    
        return (
            <div className="comments">
                <div className="write">
                    <img src={currentUser.profilePic} alt="" />
                    <input type="text" placeholder='write a comment' />
                    <button>Send</button>
                </div>
                {comments.map(comment => (
                    <div className="comment">
                        <img src={comment.profilePic} alt="" />
                        <div className="comment-info">
                            <span>{comment.name}</span>
                            <p>{comment.desc}</p>
                        </div>
                        <span className='date'>1 hour ago</span>
                    </div>
                ))}
            </div>
        )
    }
    
  • comments 的样式

    @import '../../style.scss';
    
    .comments {
        @include themify($themes) {
            img {
                width: 40px;
                height: 40px;
                border-radius: 50%;
                object-fit: cover;
            }
    
            .write {
                display: flex;
                align-items: center;
                justify-content: space-between;
                gap: 20px;
                margin: 20px 0px;
    
                input {
                    flex: 5;
                    padding: 10px;
                    border: 1px solid themed('border');
                    background-color: transparent;
                    color: themed('textColor');
                }
    
                button {
                    border: none;
                    background-color: #5271ff;
                    color: #fff;
                    padding: 10px;
                    cursor: pointer;
                    border-radius: 3px;
                }
            }
    
            .comment {
                display: flex;
                margin: 30px 0;
                justify-content: space-between;
                gap: 20px;
    
                .comment-info {
                    display: flex;
                    flex: 6;
                    flex-direction: column;
                    align-items: flex-start;
                    gap: 3px;
    
                    span {
                        font-weight: 500;
                    }
    
                    p {
                        color: themed('textColorSoft');
                    }
                }
    
                .date {
                    flex: 1;
                    display: flex;
                    align-self: center;
                    color: gray;
                    font-size: 12px;
                }
            }
        }
    }
    

用户页面

用户页面主要分为两个部分,用户信息与 posts 组件

React Social Media App 静态页面部分

用户信息又分为两个部分,imagesuser-info

  • jsx

    import Posts from '../../components/posts/posts';
    import './profile.scss'
    import plaxo from '../../assets/imgs/plaxo.png';
    import linkedin from '../../assets/imgs/linkedin.png';
    import facebook from '../../assets/imgs/facebook.png';
    import instagram from '../../assets/imgs/instagram.png';
    import twiter from '../../assets/imgs/twitter.png';
    
    export default function Profile() {
        return (
            <div className='profile'>
                <div className="images">
                    <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/wallhaven-x8v7vo.jpg" className='cover'/>
                    <img src="https://cdn.jsdelivr.net/gh/Dklns/ImgHosting/Blog-PIC/005WWep7gy1fx7zt06lm8j31hc0u0npd.jpg" className='profilePic'/>
                </div>
                <div className="profile-container">
                    <div className="profile-user-info">
                        <div className="left">
                        <a href="http://facebook.com">
                            <img src={instagram} alt="" />
                        </a>
                        <a href="http://facebook.com">
                            <img src={twiter} alt="" />
                        </a>
                        <a href="http://facebook.com">
                            <img src={facebook} alt="" />
                        </a>
                        <a href="http://facebook.com">
                            <img src={plaxo} alt="" />
                        </a>
                        <a href="http://facebook.com">
                            <img src={linkedin} alt="" />
                        </a>
                        </div>
                        <div className="center">
                            <span>Jane Doe</span>
                            <div className="info">
                                <div className="item">
                                    <i className="iconfont">&#xe602;</i>
                                    <span>USA</span>
                                </div>
                                <div className="item">
                                    <i className="iconfont">&#xe654;</i>
                                    <span>lama.dev</span>
                                </div>
                            </div>
                            <button>Follow</button>
                        </div>
                        <div className="right">
                            <i className="iconfont">&#xe61c;</i>
                            <i className="iconfont">&#xe719;</i>
                        </div>
                    </div>
                    <Posts/>
                </div>
            </div>
        )
    }
    
  • 样式

    @import '../../style.scss';
    
    @font-face {
        font-family: 'iconfont';
        /* Project id 3591478 */
        src: url('//at.alicdn.com/t/c/font_3591478_qgl1ezx8v2r.woff2?t=1679556339249') format('woff2'),
            url('//at.alicdn.com/t/c/font_3591478_qgl1ezx8v2r.woff?t=1679556339249') format('woff'),
            url('//at.alicdn.com/t/c/font_3591478_qgl1ezx8v2r.ttf?t=1679556339249') format('truetype');
    }
    
    .iconfont {
        font-family: "iconfont" !important;
        font-size: 16px;
        font-style: normal;
        -webkit-font-smoothing: antialiased;
        -webkit-text-stroke-width: 0.2px;
        -moz-osx-font-smoothing: grayscale;
    }
    
    .profile {
        @include themify($themes) {
            background-color: themed('bgSoft');
    
            .images {
                width: 100%;
                height: 300px;
                position: relative;
    
                .cover {
                    width: 100%;
                    height: 100%;
                    object-fit: cover;
                }
    
                .profilePic {
                    width: 200px;
                    height: 200px;
                    border-radius: 50%;
                    object-fit: cover;
                    position: absolute;
                    left: 0;
                    right: 0;
                    margin: auto;
                    top: 200px;
                }
            }
    
            .profile-container {
                padding: 20px 70px;
    
                @include mobile {
                    padding: 10px;
                }
    
                @include tablet {
                    padding: 20px;
                }
    
                .profile-user-info {
                    height: 180px;
                    -webkit-box-shadow: -14px 8px 55px -27px rgba(0, 0, 0, 0.37);
                    -moz-box-shadow: -14px 8px 55px -27px rgba(0, 0, 0, 0.37);
                    box-shadow: -14px 8px 55px -27px rgba(0, 0, 0, 0.37);
                    border-radius: 20px;
                    background-color: themed('bg');
                    color: themed('textColor');
                    padding: 50px;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    margin-bottom: 20px;
    
                    @include mobile {
                        flex-direction: column;
                        height: 30vh;
                        padding: 20px;
                        margin-top: 100px;
                    }
    
                    .left {
                        flex: 1;
                        display: flex;
                        gap: 10px;
    
                        @include tablet {
                            flex-wrap: wrap;
                        }
                    }
    
                    .center {
                        flex: 1;
                        display: flex;
                        flex-direction: column;
                        align-items: center;
                        gap: 10px;
    
                        span {
                            font-size: 30px;
                            font-weight: 500;
                        }
    
                        .info {
                            width: 100%;
                            display: flex;
                            align-items: center;
                            justify-content: space-around;
    
                            .item {
                                display: flex;
                                justify-content: center;
                                gap: 5px;
                                color: themed('textColorSoft');
    
                                span {
                                    font-size: 12px;
                                }
                            }
                        }
    
                        button {
                            border: none;
                            background-color: #5271ff;
                            color: #fff;
                            padding: 10px 20px;
                            border-radius: 5px;
                            cursor: pointer;
                        }
                    }
    
                    .right {
                        flex: 1;
                        display: flex;
                        align-items: center;
                        justify-content: flex-end;
                        gap: 10px;
                    }
                }
            }
        }
    }
    

响应式

页面面向设备种类假设只有三种移动手机,平板,电脑

电脑的话就是已有的样式即可,另外两种则在 style.scss 文件中写入 @media

@mixin mobile {
    @media(max-width: 480px) {
        @content;
    }
}

@mixin tablet {
    @media(max-width: 960px) {
        @content;
    }
}

然后到需要响应式的样式文件中添加响应式样式

// navBar.scss
.search {
            input {
                **@include mobile {
                    display: none;
                }

                @include tablet {
                    width: 200px;
                }**
            }
        }
.right {

          @include mobile {
              display: none;
          }

          .user {
              @include tablet {
                  display: none;
              }

          }
      }
    }

还需要修改的组件有,profile 的用户信息,leftBar rightBar