React構(gòu)建個(gè)人博客

前言

在學(xué)習(xí)react的過程中,深深的被react的函數(shù)式編程的模式所吸引,一切皆組件,所有的東西都是JavaScript。React框架其實(shí)功能很單一,主要負(fù)責(zé)渲染的功能,但是社區(qū)很活躍,衍生出了很多優(yōu)秀的庫和工具。個(gè)人覺得,想要做好一個(gè)項(xiàng)目,往往需要其他庫和工具的配合,例如redux管理數(shù)據(jù),react-router管理路由等,掌握基本的webpack配置es6語法,然后想要提高性能,還有配合react的鉤子函數(shù)和immutable.js,什么時(shí)候組件不需要重新渲染,next.js服務(wù)端渲染等等...
一直有一個(gè)想法就是重構(gòu)自己的博客,剛好這段時(shí)間放假,又剛好學(xué)習(xí)了react,于是就有了這個(gè)項(xiàng)目。

項(xiàng)目地址https://github.com/k-water/react-blog
如果覺得不錯(cuò)的話,您可以點(diǎn)右上角 "Star" 支持一下 謝謝! _

技術(shù)棧

前端

  • react
  • react-redux
  • react-thunk
  • react-router
  • axios
  • eslint
  • maked
  • highlight.js
  • antd
  • es6/7/8

后臺(tái)

  • spring boot

此項(xiàng)目采用前后端分離的實(shí)現(xiàn),后臺(tái)接口基于RESTful規(guī)范設(shè)計(jì),只提供數(shù)據(jù),前端負(fù)責(zé)路由跳轉(zhuǎn),權(quán)限限制,渲染數(shù)據(jù)等。PS:由于我是個(gè)前端er,所以這里主要講的是前端。

實(shí)現(xiàn)的功能

  • [x] admin增刪查改博客
  • [x] 博客標(biāo)簽
  • [x] 博客內(nèi)容markdown
  • [x] 博客內(nèi)容頁展示目錄
  • [x] 返回頂部
  • [x] markdown代碼高亮
  • [x] 用戶登錄注冊(cè)
  • [x] 用戶評(píng)論
  • [x] 響應(yīng)式

TODO

  • [ ] 博客分類
  • [ ] 點(diǎn)擊標(biāo)簽搜索相關(guān)博客
  • [ ] 優(yōu)化首頁側(cè)邊欄
  • [ ] 完善歸檔

效果預(yù)覽

首頁

image

內(nèi)容頁

image

用戶登錄

image

用戶評(píng)論

image

后臺(tái)管理

image
image
image

個(gè)人總結(jié)

markdown渲染

在前端渲染markdown的時(shí)候遇到了一點(diǎn)問題,相關(guān)的包很多,但是各種包解析的結(jié)果都有差異,react周邊社區(qū)推薦的是react-markdown,使用方法也很簡單

import ReactMarkdown from 'react-markdown'

const input = '# This is a header\n\nAnd this is a paragraph'
ReactDOM.render(
    <ReactMarkdown source={input} />,
    document.getElementById('container')
)

但是發(fā)現(xiàn)react-markdown對(duì)表格的支持不太友好,最后采用了marked,結(jié)合highlight.js對(duì)代碼部分實(shí)現(xiàn)高亮

import marked from 'marked'
import hljs from 'highlight.js'
  componentWillMount() {
    marked.setOptions({
      highlight: code => hljs.highlightAuto(code).value
    })
  }

最后解析出來的是一個(gè)字符串,還需要將它插入dom中,由于安全問題,React不提倡將字符串直接插入dom中,但React保留了一個(gè)API,可以這樣做:

<div className="article-detail" 
  dangerouslySetInnerHTML={{ __html: marked(output)) }} />

React組件化

react的組件由dom視圖和state組成,state是數(shù)據(jù)中心,它的狀態(tài)決定著視圖的狀態(tài)。react只負(fù)責(zé)UI的渲染,與其他框架監(jiān)聽數(shù)據(jù)動(dòng)態(tài)改變dom不同,react采用setState來控制視圖的更新。setState會(huì)自動(dòng)調(diào)用render函數(shù),觸發(fā)視圖的重新渲染,如果僅僅只是state數(shù)據(jù)的變化而沒有調(diào)用setState,并不會(huì)觸發(fā)更新。說到組件,就必須了解react組件的生命周期,官方的圖解如下:

image

關(guān)于這部分的解釋網(wǎng)上有很多,可以自行查閱。而我在開發(fā)過程用的最多的就是

  • componentWillMount()
  • componentDidMount()
  • shouldComponentUpdate(nextProps, nextState)
    這幾個(gè)鉤子函數(shù)了,關(guān)于性能優(yōu)化,可以在shouldComponentUpdate上作文章,由于shouldComponentUpdate默認(rèn)返回true,簡單的方法可以通過比較更新前后的數(shù)據(jù)結(jié)構(gòu)是否相同來判斷組件是否需要重新渲染,這時(shí)候就可以采用immutable.js了。

組件之間通信

react是單向數(shù)據(jù)流,自上而下的傳遞數(shù)據(jù)。解決復(fù)雜組件之間通信的方法有很多。一般父子組件通信是最簡單的,父組件將一個(gè)回調(diào)函數(shù)傳遞給子組件,子組件通過this.props直接調(diào)用該函數(shù)與父組件通信。

如果組件之間嵌套很深,可以使用上下文getChildContext來傳遞信息,這樣在不需要將函數(shù)一層層往下傳,任何一層的子級(jí)都可以通過this.context直接訪問,react-redux內(nèi)部實(shí)現(xiàn)就是利用此方法。

兄弟組件之間無法直接通信,它們需要利用同一層的上級(jí)作為中轉(zhuǎn)站。

Redux

redux不是必須的,如果不是復(fù)雜的組件通信,邏輯簡單,用context就行。redux并不是react特有的,其他框架也可以使用redux。當(dāng)初為了學(xué)習(xí)redux花費(fèi)了不少時(shí)間,一開始并不理解redux中間的操作,看了很多前輩們寫的文章才逐漸明白。簡單說說redux。
redux由三部分組成:store, reducer, action

image

store是一個(gè)對(duì)象,它主要由三個(gè)方法:
dispatch
用于action的分發(fā),當(dāng)action傳入dispatch會(huì)立即執(zhí)行,有些時(shí)候我們不想它立刻觸發(fā),可以在createStore中使用middleware中間件對(duì)dispatch進(jìn)行改造,例如redux-thunk,不過這是react-radux做的事了。
subscribe
顧名思義,監(jiān)聽器,監(jiān)聽state的變化,這個(gè)函數(shù)在store調(diào)用dispatch時(shí)會(huì)注冊(cè)一個(gè)listener監(jiān)聽state變化。
getState
獲取store中的state,當(dāng)我們用action觸發(fā)reducer改變了state時(shí),需要拿到新的state里面的數(shù)據(jù)。getState在兩個(gè)地方會(huì)用到,一是通過dispatch提交action后store需要拿到state里面的數(shù)據(jù),二是利用subscribe監(jiān)聽到state發(fā)生變化后調(diào)用它來獲取新的state數(shù)據(jù)。

說了這么多,store的核心代碼其實(shí)很短:

/**
 * 應(yīng)用觀察者模式
 * @param {Object} state
 * @param {Function} reducer
 */
function createStore(reducer) {
  let state = null
  const listeners = []
  const subscribe = listener => listeners.push(listener)
  const getState = () => state
  const dispatch = action => {
    // 覆蓋原對(duì)象
    state = reducer(state, action)
    listeners.forEach(listener => listener())
  }
  // 初始化state
  dispatch({})
  return {
    getState,
    dispatch,
    subscribe
  }
}

另一部分,reducer是一個(gè)純函數(shù)(pure function),它接收一個(gè)state和action作為參數(shù),根據(jù)action的type返回一個(gè)新的state,如果傳入的action type沒有匹配到,則返回默認(rèn)的state,簡單實(shí)現(xiàn)如下:

function reducer(state, action) {
  if (!state) {
    return {
      title: {
        text: "water make redux",
        color: "red"
      },
      content: {
        text: "water make redux",
        color: "green"
      }
    }
  }
  switch (action.type) {
    case "UPDATE_TITLE_TEXT":
      return {
        ...state,
        title: {
          ...state.title,
          text: action.text
        }
      }
    case "UPDATE_TITLE_COLOR":
      return {
        ...state,
        title: {
          ...state.title,
          color: action.color
        }
      }
    default:
      return state
  }
}

action比較簡單,它返回一個(gè)對(duì)象,其中type屬性是必須的,同時(shí)也可以傳入一些其他的數(shù)據(jù)。
使用例子如下:

/ 生成store
const store = createStore(reducer)
let oldState = store.getState()
// 監(jiān)聽數(shù)據(jù)變化重新渲頁面
store.subscribe(() => {
  const newState = store.getState()
  renderApp(newState, oldState)
  oldState = newState
})
// 首次渲染頁面
renderApp(store.getState())
store.dispatch({
  type: "UPDATE_TITLE_TEXT",
  text: "water is fighting"
})
store.dispatch({
  type: "UPDATE_TITLE_COLOR",
  color: "#f00"
})

React-redux

react-redux則是對(duì)redux做了封裝,可以在react中直接使用,并且提供了Providerconnect。
Provider是一個(gè)組件,它接受store作為props,然后通過context往下傳,這樣react中任何組件都可以通過context獲取store。
connect是一個(gè)函數(shù),也是一個(gè)高階組件(HOC),通過傳入state和dispatch返回一個(gè)新的組件,它的寫法是如下:

connect(mapStateToProps, mapDispatchToProps, mergeProps, options)(component)

也可以采用裝飾器的寫法,這需要babel的支持:

@connect(
    state,
    { func }
)

具體的不多介紹,迷你實(shí)現(xiàn)可以看看這個(gè)項(xiàng)目:https://github.com/k-water/make-react-redux

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容