(19)写文章页开发——简易布局和“登录鉴权” | React.js 项目实战:PC 端“简书”开发

3,947 阅读5分钟
转载请注明出处,未经同意,不可修改文章内容。

🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。


1 “路由”跳转

就“写文章页”的布局而言,我依然不会倾注太多精力,重点依然在——拿到一个新组件,我们该如何一步步思考,以及后边的整个“逻辑”该如何编码。

1️⃣打开 src 目录下的 pages 文件夹,在其中新增一个 write 文件夹。用于表示可跳转的 write 页面:
🔗前置知识:《首页开发——① 如何在 React 中使用“路由”》

19-01.png

2️⃣在 write 文件夹下,新增一个 index.js 文件,并写入以下代码:

import React, {Component} from "react";

class Write extends Component {
  render() {
    return(
      <div>写文章页~</div>
    )
  }
}

export default Write;

3️⃣打开 src 目录下的 App.js 文件:

import React, { Component } from "react";

import {GlobalStyle} from "./style";

import {GlobalIconStyle} from "./statics/iconfont/iconfont";

import {BrowserRouter, Route} from "react-router-dom";

import Header from "./common/header";
import Home from "./pages/home";
import Detail from "./pages/detail";
import Login from "./pages/login";

// 3️⃣-①:引入 Write 组件;
import Write from "./pages/write";

import { Provider } from "react-redux";
import store from "./store";

class App extends Component  {  
  render() {  
    return (
      <div>
        <GlobalStyle />
        <GlobalIconStyle />
 
        <Provider store={store}>             
          <BrowserRouter>
            <div>
              <Header />     
              <Route path="/" exact component={Home}></Route>
              <Route path="/detail/:id" exact component={Detail}></Route>
              <Route path="/login" exact component={Login}></Route>

							{/*
               3️⃣-②:继续用 Route 包裹可以跳转的 write 页面;
               ❗️Route 表示“一条条的路由规则”!
                */}
							<Route path="/write" exact component={Write}></Route>

							{/*
               ❗️❗️❗️3️⃣-③:其中,path 指页面跳转的“路径”;
               									exact 指“路径”必须“精确”地匹配才“跳转”;
                                component 指将“渲染”的内容替换为“组件”。
                */}
            </div>
          </BrowserRouter>
        </Provider>

      </div>
    );
  }
}

export default App; 

返回页面查看(可以正常进入 write 页面):

19-02.gif

2 “写文章页”布局

1️⃣打开 pages 目录下 write 文件夹中的 index.js 文件:
🔗前置知识:《HTML——③ HTML 表单详解》

import React, {Component} from "react";

// ❗️从当前目录下的 style.js 中,引入“样式组件”!
import {
  WriteWrapper,
  Title,
  Editor
} from "./style";

class Write extends Component {
  render() {
    return(
      <WriteWrapper>
        <Title placeholder="输入标题" />
        <Editor>
          <textarea placeholder="输入内容"></textarea>
        </Editor>
      </WriteWrapper>
    )
  }
}

export default Write;

2️⃣在 pages 目录下的 write 文件夹中新建一个 style.js 文件,用于放置“写文章页”的样式:

import styled from "styled-components";

export const WriteWrapper = styled.div`
  width: 620px;
  margin: 0 auto;
  padding-bottom: 100px;

`;

export const Title = styled.input`
  display: inline-block;
  width: 100%;
  padding: 10px 20px;
  
  outline: none;

  font-size: 18px;
`;

export const Editor = styled.div`
  textarea {
    position: absolute;
    width: 620px;
    height: 100%;
    padding: 20px;

    background: #eee;

    border: none;
    resize: none;
    outline: none;
  }
`;

返回页面查看(“写文章页”正常显示):

19-03.gif

3 “写文章页”登录鉴权

❓需求,当点击页面右上角的“写文章”时:

  • 只有在登录状态时,才会跳转至“写文章页”;
  • 若未登录,则跳转至“登录页”。

19-04.png

✔️需求分析: 只要在 Write 组件里获取到 login 的值,我们就可以用这个“数据”来做相应的路由跳转。

1️⃣本文一开始就让“Write 组件”拥有了获取 store 中数据的“能力”——我们把 Write 组件放到了 Provider 组件之中。

打开 src 目录下的 App.js 文件查看:

import React, { Component } from "react";

import {GlobalStyle} from "./style";

import {GlobalIconStyle} from "./statics/iconfont/iconfont";

import {BrowserRouter, Route} from "react-router-dom";

import Header from "./common/header";
import Home from "./pages/home";
import Detail from "./pages/detail";
import Login from "./pages/login";

import Write from "./pages/write";

import { Provider } from "react-redux";
import store from "./store";

class App extends Component  {  
  render() {  
    return (
      <div>
        <GlobalStyle />
        <GlobalIconStyle />
 
        <Provider store={store}>             
          <BrowserRouter>
            <div>
              <Header />     
              <Route path="/" exact component={Home}></Route>
              <Route path="/detail/:id" exact component={Detail}></Route>
              <Route path="/login" exact component={Login}></Route>

              <Route path="/write" exact component={Write}></Route> {/* ❗️❗️❗️ */}

            </div>
          </BrowserRouter>
        </Provider>

      </div>
    );
  }
}

export default App; 

2️⃣既然有了“能力”,Write 组件就可以“连接”store,然后从 store 中取得“数据项” login 的值。

2️⃣-①:打开 write 目录下的 index.js 文件;

import React, {Component} from "react";

import {
  WriteWrapper,
  Title,
  Editor
} from "./style";

/*
2️⃣-②:从 react-redux 中引入 connect 方法(它也是 React-redux 的核心 API 之一),
connect 的目的很明确——就是“连接”的意思!
 */
import {connect} from "react-redux";

/*
❗️❗️❗️2️⃣-⑭:幸运的是,react-reouter-dom 为我们提供了一个“组件”——Redirect,
它可以直接“重定向”至目的页;
 */
import {Redirect} from "react-router-dom";

class Write extends Component {
  render() {
    
    /*
    ❗️❗️❗️2️⃣-⑨:“映射”上了过后,我们就可以通过调用 this.props.login 来“调用”
    store 中的 login 了!
    注释掉下边的代码~
    return(
      <WriteWrapper>
        <Title placeholder="输入标题" />
        <Editor>
          <textarea placeholder="输入内容"></textarea>
        </Editor>
      </WriteWrapper>
    )
     */
    // ❗️❗️❗️2️⃣-⑩:取而代之,我们用一个“条件判断”来决定“页面渲染”和“路由跳转”;
    if(this.props.login) { // 2️⃣-⑪:如果为“已登录”状态,则“渲染”出“写文章页”;
      return(
        <WriteWrapper>
          <Title placeholder="输入标题" />
          <Editor>
            <textarea placeholder="输入内容"></textarea>
          </Editor>
        </WriteWrapper>
      )
    }else { // 2️⃣-⑫:否则(为“未登录”状态),则“重定向”至“登录页”;
      // ❓2️⃣-⑬:怎样“重定向”至“登录页”呢?
      
      // 2️⃣-⑮:我们用“Redirect 组件”来“重定向”至“登录页”;
      return <Redirect to="/login" />; // ❗️❗️❗️注意标签的闭合!
    }
  }
}

// 2️⃣-⑥:接下来,我们先定义“连接”的“规则”;
const mapStateToProps = (state) => { /*
																		 2️⃣-⑦:把 store 里的“数据 state”作为“参数”
                                     传递给 mapStateToProps;
																			*/
  
  return { // ❗️这个规则会返回一个“对象”出去!
		login: state.getIn(["login", "login"]) /*
    																			 ❗️❗️❗️2️⃣-⑧:“规则”的具体做法为——将 store 
                                           里的 login 映射到“Write 组件”里的 props 的
                                           login 中去;
    																				*/  
  }

}


/*
2️⃣-③:之前我们直接导出的是 Write,可用了 React-redux 后,就不能这样写了!
export default Write;
 */

/*
2️⃣-④:取而代之,我们是导出 connect 方法(
❗️注意看我们给 connect 方法传递了哪些参数!)
 */
export default connect(mapStateToProps, null)(Write); /*
																			2️⃣-⑤:我们一共要给 connect 方法传递 3 个参数!
                                      Write 表示:connect 会让“Write 组件”和 store 进行
                                      “连接”;
                                      
                                      mapStateToProps 表示:“Write 组件”和 store 进行
                                      “连接”是需要“规则”的,而具体的“规则”就在这个 
                                      mapStateToProps 里边(❗️直译为:把 store 里边的
                                      “数据 state”映射到“Write 组件”的 props 里);
                                      
                                      null 表示:这里还会接收一个名叫 mapDispatchToProps
                                      的参数,由于这里不涉及到“修改数据”,故用 null 占位。
																											 */

2️⃣-⑯:打开 header 目录下的 index.js 文件;

import React, {Component} from "react";

import {Link} from "react-router-dom";  

import {
  HeaderWrapper,
  Logo,
  
  Navbar,
  ItemList,
  LinkList,
  
  SearchArea,
  SearchInput,
  SearchPanel,
  PanelTitle,
  PanelChange,
  PanelLabels,
  LabelLink,
  
  Extra,
  ExtraLink
  
} from "./style";

import { connect } from "react-redux";

import {actionCreators} from "./store";

import {actionCreators as loginActionCreators} from "../../pages/login/store";

class Header extends Component {
  
  getPanels() {
    const newList = this.props.list.toJS();
    const pageLabels = [];  
    
    if(newList.length) {  
      for(let i=(this.props.page - 1)*10; i<this.props.page*10; i++) { 
        pageLabels.push(  
          <Link to="/"  key={newList[i]}>  
            <LabelLink>  
              {newList[i]} 
            </LabelLink>
          </Link>
        )
      }
      return pageLabels; 
    }
  } 
  
  render() {
    return (
      <HeaderWrapper>

        <Link to="/">  
          <Logo>
            <img src="https://qdywxs.github.io/jianshu-images/logo.png" alt="logo" />
          </Logo>
        </Link>

        <Navbar className="clearfix">
          <ItemList className="active">
            <Link to="/">  
              <LinkList href="/">
                首页
              </LinkList>
            </Link>
          </ItemList>

          <ItemList>
            <Link to="/">  
              <LinkList>
                下载APP
              </LinkList>  
            </Link>         
          </ItemList>
        </Navbar>
      
        <SearchArea>

          <SearchInput
            onFocus={() => this.props.handleInputFocus(this.props.list)}
          />
      
          <span className="iconfont icon-search">&#xe63e;</span>
      
          <SearchPanel>
            <PanelTitle>
              热门搜索
      
              <PanelChange
                onMouseDown={this.props.handleMouseDown}
                onMouseUp={this.props.handleMouseUp}

                onClick={() => this.props.handleChangePage(this.props.page, this.props.totalPage)}
              >  
                  
                <span className={this.props.refresh ? "iconfont refresh" : "iconfont"}>&#xe65f;</span>
                换一批
              </PanelChange>
            </PanelTitle>
      
            <PanelLabels className="clearfix">
              {this.getPanels()}
            </PanelLabels>
          </SearchPanel>
        </SearchArea>
      
      
        <Extra>
          <span className="iconfont icon-textsize" >&#xe739;</span>
          {this.props.login ? <ExtraLink className="login" onClick={this.props.logout}>退出</ExtraLink> : 
            <Link to="/login">
              <ExtraLink className="login">
                登录
              </ExtraLink>
            </Link>
          }

          <Link to="/">  
            <ExtraLink className="register">
              注册
            </ExtraLink> 
          </Link>

					{/*
           ❗️❗️❗️2️⃣-⑰:将“写文章”的 Link 标签的跳转“路径”改为 /write;
           先注释掉下面的写法~
          <Link to="/">  
            */}
					<Link to="/write">
            
            <ExtraLink className="writing">
              <span className="iconfont icon-pen">&#xe600;</span>
              写文章
            </ExtraLink>  
          </Link>  

        </Extra>
      </HeaderWrapper>
    )
  }
}

const mapStateToProps = (state) => { 
  return { 
    refresh: state.getIn(["header", "refresh"]),
    list: state.getIn(["header", "list"]),
    
    page: state.getIn(["header", "page"]),
    totalPage: state.getIn(["header", "totalPage"]),

    login: state.getIn(["login", "login"])
    
  }
}

const mapDispatchToProps = (dispatch) => {  
  return {
    handleMouseDown() { 
      const action = actionCreators.changeClassNameAction(); 
      dispatch(action)
    
    },

    handleMouseUp() {
      const action = actionCreators.resumeClassNameAction();
      dispatch(action)
    },
    
    
    handleInputFocus(list) {  
      if(list.size === 0) { 
        const action = actionCreators.initLabelAction();
        dispatch(action)
      }
    },
    
    handleChangePage(page, totalPage) {
      if(page < totalPage) {
        dispatch(actionCreators.changePageAction(page + 1))
      }else {
        dispatch(actionCreators.changePageAction(1))
      }
    },
    

    logout() {
      const action = loginActionCreators.logout();
      dispatch(action);
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Header); 

返回页面查看(需求全部正确实现):

19-05.gif

下一篇,我们对整个项目作性能的优化和上线,也是很重要的一篇。

祝好,qdywxs ♥ you!