React構建個人部落格

waterc發表於2019-03-03

前言

在學習react的過程中,深深的被react的函數語言程式設計的模式所吸引,一切皆元件,所有的東西都是JavaScript。React框架其實功能很單一,主要負責渲染的功能,但是社群很活躍,衍生出了很多優秀的庫和工具。個人覺得,想要做好一個專案,往往需要其他庫和工具的配合,例如redux管理資料,react-router管理路由等,掌握基本的webpack配置es6語法,然後想要提高效能,還有配合react的鉤子函式和immutable.js,什麼時候元件不需要重新渲染,next.js服務端渲染等等…
一直有一個想法就是重構自己的部落格,剛好這段時間放假,又剛好學習了react,於是就有了這個專案。

專案地址github.com/k-water/rea…

如果覺得不錯的話,您可以點右上角 “Star” 支援一下 謝謝! ^_^

技術棧

前端

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

後臺

  • spring boot

此專案採用前後端分離的實現,後臺介面基於RESTful規範設計,只提供資料,前端負責路由跳轉,許可權限制,渲染資料等。PS:由於我是個前端er,所以這裡主要講的是前端。

實現的功能

  • [x] admin增刪查改部落格
  • [x] 部落格標籤
  • [x] 部落格內容markdown
  • [x] 部落格內容頁展示目錄
  • [x] 返回頂部
  • [x] markdown程式碼高亮
  • [x] 使用者登入註冊
  • [x] 使用者評論
  • [x] 響應式

TODO

  • [ ] 部落格分類
  • [ ] 點選標籤搜尋相關部落格
  • [ ] 優化首頁側邊欄
  • [ ] 完善歸檔
  • [ ] 部署上線

效果預覽

首頁

React構建個人部落格

內容頁

React構建個人部落格

使用者登入

React構建個人部落格

使用者評論

React構建個人部落格

後臺管理

React構建個人部落格
React構建個人部落格
React構建個人部落格

個人總結

markdown渲染

在前端渲染markdown的時候遇到了一點問題,相關的包很多,但是各種包解析的結果都有差異,react周邊社群推薦的是react-markdown,使用方法也很簡單

import ReactMarkdown from `react-markdown`

const input = `# This is a header

And this is a paragraph`
ReactDOM.render(
    <ReactMarkdown source={input} />,
    document.getElementById(`container`)
)
複製程式碼

但是發現react-markdown對錶格的支援不太友好,最後採用了marked,結合highlight.js對程式碼部分實現高亮

import marked from `marked`
import hljs from `highlight.js`
  componentWillMount() {
    marked.setOptions({
      highlight: code => hljs.highlightAuto(code).value
    })
  }
複製程式碼

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

<div className="article-detail" 
  dangerouslySetInnerHTML={{ __html: marked(output)) }} />
複製程式碼

React元件化

react的元件由dom檢視和state組成,state是資料中心,它的狀態決定著檢視的狀態。react只負責UI的渲染,與其他框架監聽資料動態改變dom不同,react採用setState來控制檢視的更新。setState會自動呼叫render函式,觸發檢視的重新渲染,如果僅僅只是state資料的變化而沒有呼叫setState,並不會觸發更新。說到元件,就必須瞭解react元件的生命週期,官方的圖解如下:

React構建個人部落格

關於這部分的解釋網上有很多,可以自行查閱。而我在開發過程用的最多的就是

  • componentWillMount()
  • componentDidMount()
  • shouldComponentUpdate(nextProps, nextState)
    這幾個鉤子函式了,關於效能優化,可以在shouldComponentUpdate上作文章,由於shouldComponentUpdate預設返回true,簡單的方法可以通過比較更新前後的資料結構是否相同來判斷元件是否需要重新渲染,這時候就可以採用immutable.js了。

元件之間通訊

react是單向資料流,自上而下的傳遞資料。解決複雜元件之間通訊的方法有很多。一般父子元件通訊是最簡單的,父元件將一個回撥函式傳遞給子元件,子元件通過this.props直接呼叫該函式與父元件通訊。

如果元件之間巢狀很深,可以使用上下文getChildContext來傳遞資訊,這樣在不需要將函式一層層往下傳,任何一層的子級都可以通過this.context直接訪問,react-redux內部實現就是利用此方法。

兄弟元件之間無法直接通訊,它們需要利用同一層的上級作為中轉站。

Redux

redux不是必須的,如果不是複雜的元件通訊,邏輯簡單,用context就行。redux並不是react特有的,其他框架也可以使用redux。當初為了學習redux花費了不少時間,一開始並不理解redux中間的操作,看了很多前輩們寫的文章才逐漸明白。簡單說說redux。
redux由三部分組成:store, reducer, action

React構建個人部落格

store是一個物件,它主要由三個方法:
dispatch
用於action的分發,當action傳入dispatch會立即執行,有些時候我們不想它立刻觸發,可以在createStore中使用middleware中介軟體對dispatch進行改造,例如redux-thunk,不過這是react-radux做的事了。
subscribe
顧名思義,監聽器,監聽state的變化,這個函式在store呼叫dispatch時會註冊一個listener監聽state變化。
getState
獲取store中的state,當我們用action觸發reducer改變了state時,需要拿到新的state裡面的資料。getState在兩個地方會用到,一是通過dispatch提交action後store需要拿到state裡面的資料,二是利用subscribe監聽到state發生變化後呼叫它來獲取新的state資料。

說了這麼多,store的核心程式碼其實很短:

/**
 * 應用觀察者模式
 * @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 => {
    // 覆蓋原物件
    state = reducer(state, action)
    listeners.forEach(listener => listener())
  }
  // 初始化state
  dispatch({})
  return {
    getState,
    dispatch,
    subscribe
  }
}
複製程式碼

另一部分,reducer是一個純函式(pure function),它接收一個state和action作為引數,根據action的type返回一個新的state,如果傳入的action type沒有匹配到,則返回預設的state,簡單實現如下:

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比較簡單,它返回一個物件,其中type屬性是必須的,同時也可以傳入一些其他的資料。
使用例子如下:

/ 生成store
const store = createStore(reducer)
let oldState = store.getState()
// 監聽資料變化重新渲頁面
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則是對redux做了封裝,可以在react中直接使用,並且提供了Providerconnect
Provider是一個元件,它接受store作為props,然後通過context往下傳,這樣react中任何元件都可以通過context獲取store。
connect是一個函式,也是一個高階元件(HOC),通過傳入state和dispatch返回一個新的元件,它的寫法是如下:

connect(mapStateToProps, mapDispatchToProps, mergeProps, options)(component)
複製程式碼

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

@connect(
	state,
	{ func }
)
複製程式碼

具體的不多介紹,迷你實現可以看看這個專案:https://github.com/k-water/make-react-redux

相關文章