前言
在學(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ù)覽
首頁

內(nèi)容頁

用戶登錄

用戶評(píng)論

后臺(tái)管理



個(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組件的生命周期,官方的圖解如下:

關(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

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中直接使用,并且提供了Provider和connect。
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