[toc] 1. 搭建環境 2. React知識點 1. 組件 組件:就是將整個UI拆分為很多小的,可重用的UI,每一個小的UI獨立做自己的事情。 1.1 定義一個組件 一個組件需要繼承 重寫 函數,返回 註意點: 1.只要有JSX的地方必須引入 2.返回值中只能有一個元素包裹, 包裹整個JSX,如 ...
目錄
1. 搭建環境
npm install -g creat-react-app
# 創建一個項目
create-react-app jianshu
cd jianshu
npm run start
# 安裝styled-components 全局統一管理css
npm install --save styled-components
# 全局樣式引入
https://meyerweb.com/eric/tools/css/reset/
background-size: contain;
2. React知識點
1. 組件
組件:就是將整個UI拆分為很多小的,可重用的UI,每一個小的UI獨立做自己的事情。
1.1 定義一個組件
一個組件需要繼承React.Component
重寫render()
函數,返回JSX
註意點:
1.只要有JSX的地方必須引入
React
2.返回值中只能有一個元素包裹,
div
包裹整個JSX,如下是錯誤的返回方式return ( <div> hello world </div> <div> hello world </div> );
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>
hello world
</div>
);
}
}
export default App;
1.2 組合與拆分組件
引用官方的一句話:
Don’t be afraid to split components into smaller components.
儘可能的讓每一個組件都能分工明確,減少重覆,加大可重用。例如表格組件,就必須被拆分為一個單獨的組件,它可能在各個地方被用到。
組件拆分優點:讓開發顯得更加清晰明瞭。
未拆分組件看起來就沒有食欲
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
1.案例解析
將首頁拆分為Header, Right,如下定義一個首頁組件,在首頁組件中分別引入Header和Right組件,這就是一個簡單的組件拆分和組合.
<React.Fragment />
是一個虛擬的組件,僅僅是為了包裹其中的元素,並不會出現在頁面上
Index.js
import React from 'react'
import Header from './Header'
import Right from './Right'
class Index extends React.Component {
render() {
return (
<React.Fragment>
<Header />
<Right />
</React.Fragment>
);
}
}
export default Index
Header.js
import React from 'react'
class Header extends React.Component {
render() {
return (
<React.Fragment>
<div>i am header component</div>
</React.Fragment>
);
}
}
export default Header
Right.js
import React from 'react'
class Right extends React.Component {
render() {
return (
<React.Fragment>
<div>i am right component</div>
</React.Fragment>
);
}
}
export default Right
1.3 組件傳值
組件傳值分為以下幾種情況:1. 父組件傳值到子組件;2.子組件向父組件傳值;3.隔代組件傳值
1. 父傳子
註意點: props是只讀的
例如Index.js
向Header.js
組件傳值
{/* Index.js 傳遞title屬性 */}
return (
<React.Fragment>
<Header title="Header Title"/>
<Right />
</React.Fragment>
);
{/*Header.js 接受值*/}
return (
<React.Fragment>
<div>{this.props.title}</div>
</React.Fragment>
);
1.4 state
state可以用來存儲數據,記性狀態管理。
it is private and fully controlled by the component.
render函數執行:組件state和props發生改變,render函數會被執行,當父組件重新渲染時候,子組件render函數也會被重新執行
class Index extends React.Component {
constructor(props) {
super(props)
this.state = {
title: 'Header title'
}
}
render() {
return (
<React.Fragment>
<Header title={this.state.title}/>
<Right />
</React.Fragment>
);
}
}
export default Index
1.5 PropTypes
當組件之間傳值的時候,接收一方往往需要對參數進行校驗,這就是PropTypes的作用
import PropTypes from 'prop-types'
class ReactUI extends React.Component {
render() {
return (
)
}
}
//屬性類型
ReactUI.propTypes = {
//表示name必須是string類型
name: PropTypes.string
//屬性必傳
id: PropTypes.element.isRequired
}
預設值
The
propTypes
typechecking happens afterdefaultProps
are resolved
class Greeting extends React.Component {
static defaultProps = {
name: 'stranger'
}
render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}
1.5 生命周期函數
在頁面載入的過程中,特定時間點自動執行的函數稱為生命周期函數。
生命周期的四個階段:
- Initialization
這一階段主要載入props和state
- Mounting:頁面第一次載入的時候才會執行的掛載
componentWillMount
:在組件即將被掛載到頁面的時刻執行
render
:
componentDisMount
:在組件掛載完成之後會被執行
使用場景:用於發送ajax請求
- Updation:數據發生變化的時候會被執行
- props:
componentWillReceiveProps
:
子組件從父組件接受屬性,第一次存在父組件,render函數執行,該函數不會被執行,當父組件render函數重新被執行,就會執行這個函數
shouldComponentUpdate
:組件是否需要被更新,返回boolean值
使用場景:shouldComponentUpdate(nextProps, nextState);這兩個參數來接受即將改變的props和state的值;演示:
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.title !== this.props.title) { //放生改變,則需要重新渲染
return true
} else {
return false
}
}
componentWillUpdate
: 當組件被更新之前被執行,但是取決於shouldComponentUpdate
的返回結果,
render
componentDidUpdate
:組件更新完成之後會被執行
- states:
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
:
- Unmounting
componentWillUnmount
: 組件從頁面移除的時候會被執行
1.6 無狀態組件
無狀態組件: 就是組件中只含有render()函數的組件,
import React from 'react'
class Header extends React.Component {
render() {
return (
<React.Fragment>
<div>xx {this.props.title}</div>
</React.Fragment>
);
}
}
export default Header
這種組件可以被簡化為無狀態組件
export default (props, context) => {
return (
<React.Fragment>
<div>xx {props.title}</div>
</React.Fragment>
);
}
1.7 List and Key
案例:使用List集合渲染一個頁面
key的作用:
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity
這個主要和虛擬DOM有關,可以提高虛擬DOM性能。
兄弟姐妹之間的key必須獨一無二
import React from 'react'
class Event extends React.Component {
constructor(props) {
super(props)
this.state = {
data: [1, 2, 3, 4, 5, 6, 7]
}
this.handClick = this.handClick.bind(this);
}
render() {
return (
<div>
<ul>
{
this.state.data.map((item) => (
<li key={item}>{item}</li>
))
}
</ul>
</div>
)
}
}
export default Event
2. JSX
JSX
既不是string也不是html,是React獨有的一種語法,jsx中需要註意的點:
- 標簽的類
class
如:<div class='show'></div>
會和ES6關鍵字衝突會使用className代替 <label></label>
for屬性也會使用htmlFor
代替
3. 虛擬DOM
虛擬DOM就是一個JS對象,用來描述真實DOM
jsx轉換成js對象
<div className='show'>hello world</div>
React.createElement('div',{className: 'show'}, 'hello world');
流程講解
- state數據
- jsx模板
- 數據 + 模板生成虛擬DOM
- 使用虛擬DOM來來生成真實DOM, 顯示在頁面上
- state發生改變
- 數據 + 模板生成新的虛擬DOM
- 比較原始的虛擬DOM和新的虛擬DOM之間的區別,找到不同之處
- 操作DOM,改變不同的地方
diff演算法
上面的第七步驟是如何比對不同之處呢,這裡使用了diff演算法。
同層比對:第一層有差異,則不會再進行比對,直接向下全部渲染,如果第一層相同,則向下繼續比較。
根據Key做關聯來進行比對: 同層的key必須固定不變,這樣才能再進行比對的時候準確的找到原始的dom節點。假如使用index作為key值,當刪除一個元素,那個被刪除元素後面的所有標簽key值都會被改變,那麼進行比對的時候,就會出現性能消耗。
5. 函數綁定
React events are named using camelCase, rather than lowercase.
例如
onClick
,onBlur
...
import React from 'react'
import './event.css'
class Event extends React.Component {
constructor(props) {
super(props)
this.state = {
isShow: true
}
}
render() {
return (
<div>
<button onClick={this.handClick.bind(this)}>切換</button>
<div className={this.state.isShow ? 'show':'hidden'}>你好</div>
</div>
)
}
handClick() {
this.setState((state) => ({
isShow: !state.isShow
}))
}
}
export default Event
傳遞參數
render() {
return (
<div>
<button onClick={() => this.handClick(this.state.isShow)}>切換</button>
<div className={this.state.isShow ? 'show':'hidden'}>你好</div>
</div>
)
}
handClick(isShow) {
this.setState((state) => ({
isShow: !isShow
}))
}
傳遞參數的幾種方式
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
<SearchSwitch onClick={() => switchItem(page, totalPage)}>換一批</SearchSwitch>
3. Redux
https://redux.js.org/introduction/getting-started
# 安裝
npm install --save redux
1. 瞭解是三個概念
Actions
: 就像一個快遞,裡面包含地址(行為),以及物品(數據)
Reducers
:就像一個快遞員,負責分發不同的快遞,根據地址(行為)派送到不同地方
Store
: 就像一個總站,可以存儲這些快遞,最後返回給用戶。
與物流之間的區別就是:store不可變,只可以返回數據,而原先的數據一直保留在store裡面
1.1 演示
第一步:創建Store index.js
這裡藉助redux createStore方法創建store,並且引入reducer
import { createStore } from 'redux'
import reducer from './Reducers'
const store = createStore(
reducer,
// 這個可以使用Chrome redux 插件進行調試
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default store
第二步:創建一個reducer
reducer 裡面保存數據的初始狀態,
defaultState
解析action的行為
const defaultState = {
list: ['hello', 'world']
}
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'add_list_action':
return {list: action.data}
default:
return state
}
}
export default reducer
第三步:使用redux
- this.state = store.getState() 從store中獲取數據
- store.subscribe(this.handSubscribe)訂閱數據,監聽數據的變化
handClick()
方法中主要定義action,然後利用store進行分發
import React from 'react'
import store from './store'
class ReduxUI extends React.Component {
constructor(props) {
super(props)
this.state = store.getState()
this.handClick = this.handClick.bind(this)
this.handSubscribe = this.handSubscribe.bind(this)
store.subscribe(this.handSubscribe)
}
render() {
return (
<div>
<button onClick={this.handClick}>點擊我</button>
<ul>
{
this.state.list.map((item) => {
return <li key={item}>{item}</li>
})
}
</ul>
</div>
)
}
handClick() {
//創建一個Action
const addListAction = () => ({
type: 'add_list_action',
data: ['1', '2', '3']
})
//這是一個非同步操作
store.dispatch(addListAction())
}
handSubscribe() {
this.setState(store.getState())
}
}
export default ReduxUI
2. react-redux中間件
改造1>1.1 中的代碼
nmp install --save react-redux
新增加一個父組件,統一管理store,
這個組件擁有store,
<Provider store={store}>
表示改下麵的所有元素都可以使用store
中的數據
import React, { Component } from 'react';
import store from './pages/redux/store'
import { Provider } from 'react-redux'
import ReduxUI from './pages/redux/ReduxUI'
class App extends Component {
render() {
return (
<Provider store={store}>
<ReduxUI/>
</Provider>
);
}
}
export default App;
步驟三中的修改
第一步:添加
connect
方法第三步:映射state中的屬性,定義分發的方法
第四步:連接組件
import React from 'react'
import {connect } from 'react-redux'
class ReduxUI extends React.Component {
render() {
//從react-redux中接收store中的信息,以及分發action的方法
const { list, handClick } = this.props
return (
<div>
<div>
<button onClick={handClick}>點擊我</button>
<ul>
{
list.map((item) => {
return <li key={item}>{item}</li>
})
}
</ul>
</div>
</div>
)
}
}
//state就是store中state
const mapStateToProps = (state) => ({
list: state.list
})
//這裡定義分發action的方法,dispatch 就是store的dispatch方法
const mapDispatchProps = (dispatch) => ({
handClick() {
//創建一個Action
const addListAction = () => ({
type: 'add_list_action',
data: ['1', '2', '3']
})
dispatch(addListAction())
}
})
//連接組件,並且把屬性和action行為傳遞給組件
export default connect(mapStateToProps, mapDispatchProps)(ReduxUI)
4. 其他
1. Redux-thunk
Redux Thunk middleware allows you to write action creators that return a function instead of an action
npm install redux-thunk
to enable Redux Thunk, use
applyMiddleware()
這裡配合redux-devtools-extension調試工具一起使用
import { createStore, compose, applyMiddleware } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'
// 這個就是讓調試插件配合中間件使用
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
reducer,
composeEnhancers(applyMiddleware(thunk))
)
export default store
// 定義一個方法,處理滑鼠聚焦行為
//然後dispatch分發action
handFocus(list) {
(list.size === 0) && dispatch(actionCreator.getSearchItems())
dispatch(actionCreator.inputFocusAction())
}
//另外一個文件
//===================================
// 這裡就是上面的action,這個action是一個方法,在該方法裡面發送ajax非同步請求,
//非同步請求之後繼續分發另外一個action
export const getSearchItems = () => {
return (dispatch) => {
axios.get("/api/item.json").then((res) => {
dispatch(searchItemsAction(res.data.data))
})
}
}
正如官方文檔所說:redux-thunk允許你分發一個方法類型的action
2. Redux-saga
asynchronous things like data fetching and impure things like accessing the browser cache
npm install --save redux-saga
import { createStore, compose, applyMiddleware } from 'redux'
import reducer from './Reducers'
import createSagaMiddleware from 'redux-saga'
import reduxSaga from './reduxSaga'
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
reducer,
composeEnhancers(applyMiddleware(sagaMiddleware))
)
//運行自己的reduxSaga
sagaMiddleware.run(reduxSaga)
export default store
reduxSaga.js
import { takeEvery, put } from 'redux-saga/effects'
import axios from 'axios'
function* getJson() {
const res = yield axios.get('./data.json')
yield put({type:"add_list_action", data: res.data}) //繼續派發action
}
function* reduxSaga() {
// 當store分發'add_list_action' 這個action的時候,會調用getJson方法
yield takeEvery("add_list__pre_action", getJson)
}
export default reduxSaga
5. immutable
immutable可以保證數據一旦創建,就不會被改變
npm install --save immutable
//目的:將store變成不可變,這是一個簡單reducer.js
import * as constants from './constant' //es6語法
import { fromJS } from 'immutable'
//這裡將defaultStore變成一個immutable對象
const defaultStore = fromJS({
focused: false,
mouseIn: false,
list: [],
page: 1,
totalPage: 1
})
export default (state = defaultStore, action) => {
switch (action.type) {
case constants.input_focused:
//這裡的set immutable對象的一個值,其實並不是改變了state,而是返回一個新的對象
//通過set可以簡化我們的操作,不用重新構建全部的對象
return state.set('focused', true)
default:
return state
}
}
// 如何獲取一個immutable對象呢?
const mapStateToProps = (state) => ({
//這裡表示獲取header組件下的一個immutable對象
//這裡是 redux-immutable 獲取數據的形式
// state.header.get('focused') 為immutable獲取數據的格式
focused: state.getIn(['header', 'focused']), //state.get('header').get('focused')
mouseIn: state.getIn(['header', 'mouseIn']),
list: state.getIn(['header', 'list']),
page: state.getIn(['header', 'page']),
totalPage: state.getIn(['header', 'totalPage'])
})
//通過action傳遞參數的時候也應該是一個immutable對象
export const searchItemsAction = (data) => ({
type: constants.render_list,
data: fromJS(data),
totalPage: Math.ceil(data.length / 10)
})
5.1 redux-immutable
redux-immutable
is used to create an equivalent function of ReduxcombineReducers
that works with Immutable.jsstate.
我們經常面臨很多很多組件,但是如果把所有組件的store,reducer, action 維護在一個目錄下,那將是慘目忍睹的,所以通常情況下我們會分開管理redux,然後將所有的reducer組合在一起
npm install --save redux-immutable
//import { combineReducers } from 'redux'
import { combineReducers } from 'redux-immutable' //可以管理immutale對象
import { reducer as headerReducer} from '../header/store'
const reducer = combineReducers({
header: headerReducer
})
export default reducer
5. react-router-dom
React Router is a collection of navigational components that compose declaratively with your application.
npm install --save react-router-dom
import { BrowserRouter, Route } from "react-router-dom"
...
<BrowserRouter>
{/* 表示路由到一個組件 */}
<Route path="/home" exact component={Home} />
</BrowserRouter>
Route
: 表示要路由的到哪一個組件
Link
: 表示一個連接,跳轉到某個頁面
6. react-loadable
一個項目,不同的頁面應該按需載入,而不是當首頁出來之後載入所有的組件,例如我們訪問首頁的時候只會載入首頁相關的資源,訪問詳情頁的時候載入詳情頁的代碼,這樣可以提高首頁訪問速度以及用戶體驗
npm install --save react-loadable
案例演示
import React from 'react'
import Loadable from 'react-loadable'
const LoadableComponent = Loadable({
//這裡`reactUI`是一個組件,表示這個組件將會被按需載入
loader: () => import('./reactUI'),
// 這裡是一個函數,可以是一個組件,表示頁面載入前的狀態
loading: () => <div>正在載入</div>
})
//返回無狀態組件
export default () => {
return <LoadableComponent/>
}
7. 在React 使用各種CSS
1. styled-components
npm install --save styled-components
定義全局樣式
import { createGlobalStyle } from 'styled-components'
export const ResetCSS = createGlobalStyle`
body {
line-height: 1;
}
....
`
// 引用全局樣式
import { ResetCSS } from './style'
...
render() {
return (
<ResetCSS />
)
}