在閱讀以前,會有一些小夥伴以爲疑惑,做者怎麼寫前端文章了呢,做者不是專一後端Java嗎?這是怎麼了?其實否則,在4年前,那個時候尚未流行vue和react,身爲後端程序員的咱們,無論是java仍是php程序員,都是須要寫前端的,不過那個時候的前端沒有如今那麼多東西。咱們通常叫美工畫好靜態頁面,而後交給後端程序員,後端程序員在靜態頁面中加入js代碼,或者把靜態頁面替換成jsp、velocity等靜態模板語言的代碼,一個動態效果的頁面就完成了。隨着互聯網的不斷髮展,程序員工種的劃分也愈來愈明細,如今的前端和做者曾經那個時候已經大不同了,不管是思想仍是語言風格,爲了學習下如何本身製做頁面,也爲了感覺下前端代碼的魅力,故選擇了React.js 前端框架做爲學習目標。其實前端頗有趣,有意思!身爲後端程序員的你不打算了解一下嘛~php
在學習 react 以前,咱們須要先安裝對應的運行環境,工欲善其事必先利其器。首先安裝好以下環境:css
不知道個人讀者是否是徹底不懂前端,建議讀者有一點點的 Html、Css、java script、es6 的基礎,實在沒有建議花個1~2天學習下。html
react 前端項目和咱們平時的java項目同樣,都有其本身的項目結構,java的項目結構有IDE開發工具幫咱們生產,在本文中,咱們使用facebook 的 create-react-app 腳手架項目來幫咱們生成 react 項目結構,操做以下:前端
# 全局安裝官方腳手架
npm i -g create-react-app
# 初始化建立一個基於 react 的項目
create-react-app 01_jpview_class
# 設置 npm 下載鏡像源爲淘寶, 和設置 maven 倉庫源一個意思
npm config set registry http://registry.npm.taobao.org
複製代碼
![]()
├── README.md 文檔 ├── package-lock.json ├── package.json npm 依賴 ├── public 靜態資源 │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src 源碼 ├── App.css ├── App.js 根組件 ├── App.test.js 測試 ├── index.css 全局樣式 ├── index.js 入口 ├── logo.svg └── serviceWorker.js pwa支持 複製代碼
刪除src目錄下的因此文件,新建一個 index.js文件,內容爲vue
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App/>, document.querySelector('#root'))
複製代碼
新建 App.js文件,內容爲:java
import React, { Component } from "react";
export default class App extends Component{
render(){
return <div>
<button>雷猴啊</button>
</div>
}
}
export default KaikebaCart
複製代碼
上面的代碼看起來會有感到困惑的地方,首先就是ReactDOM.render(<App />, document.querySelector('#root'));
看起來是js和html的混合體,這種語法被稱之爲JSX,其實際核心的邏輯徹底是js來實現的。node
在項目目錄終端執行如下命令能夠看到效果react
# 下載依賴包
npm install
# 啓動運行項目
npm start
複製代碼
如下全部代碼均可以直接複製到 index.js
文件中 體驗效果git
React
的世界裏一切皆是組件,咱們使用class
語法構建一個最基本的組件,組件的使用方式和HTML相同,組件的render
函數返回渲染的一個JSX頁面,而後使用ReactDom渲染到頁面裏程序員
import React from 'react';
import ReactDOM from 'react-dom';
// 繼承React.Component表示App是一個組件
class App extends React.Component {
render() {
return <div> Hello React </div>
}
}
// ReactDOM.render()方法把 App中的內容追加到 index.html 中 <div id="root">的標籤上
ReactDOM.render(<App/>, document.querySelector('#root'))
複製代碼
React
組件使用和html
相似的方式傳遞參數,在組件內部,使用this.props
獲取全部的傳遞的參數,在JSX
裏使用變量,使用{}
包裹
import React from 'react';
import ReactDOM from 'react-dom';
// 繼承React.Component表示App是一個組件
class App extends React.Component {
render() {
// 獲取<App name="React"> 傳遞過來的屬性name值
return <div> Hello {this.props.name} </div>
}
}
// ReactDOM.render()方法把 App中的內容追加到 index.html 中 <div id="root">的標籤上
ReactDOM.render(<App name="React" />, document.querySelector('#root'))
複製代碼
JSX
是一種js的語法擴展,表面上像HTML,本質上仍是經過babel
轉換爲js執行,全部在JSX
裏可使用{}
來寫js的語法,JSX
本質上就是轉換爲React.createElement
在React內部構建虛擬Dom
,最終渲染出頁面
import React from 'react';
import ReactDOM from 'react-dom';
// 繼承React.Component表示App是一個組件
class App extends React.Component {
render() {
return (
<div>
// {2+2} js的計算語法,結果爲4
Hello {this.props.name}, I am {2 + 2} years old
</div>
)
}
}
// ReactDOM.render()方法把 App中的內容追加到 index.html 中 <div id="root">的標籤上
ReactDOM.render(<App name="React" />, document.querySelector('#root'))
複製代碼
咱們到如今爲止尚未更新過UI頁面,React
內部經過this.state
變量來維護內部的狀態,而且經過this.stateState
來修改狀態,render
裏用到的state
變量,也會自動渲染到UI,咱們如今constructor()
來初始化state
,在JSX
語法裏使用this.state.num
獲取,而後jsx
裏使用onClick
綁定點擊事件,注意這裏須要在constructor()
裏使用bind()方法
綁定this
指向,而後內部調用this.setState
修改值,注意這裏不能寫成this.state.num+1,而是要調用this.setState
,設置並返回一個全新的num值。
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
constructor(props){
super(props)
// 初始化構造是設置內部狀態 num值爲 1
this.state = {
num:1
}
// 把handleClick()方法綁定到當前對象Counter上
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
// 改變內部狀態 num 的值
this.setState({
num:this.state.num + 1
})
}
render() {
return (
<div>
<p>{this.state.num}</p>
{/*{this.handleClick} js語法調用當前對象的handleClick()方法*/}
<button onClick={this.handleClick}>click</button>
</div>
)
}
}
ReactDOM.render(<Counter/>, document.querySelector('#root'))
複製代碼
在組件內部存在一些特殊的方法,會在組件的不一樣階段執行,好比組件加載完畢後會執行componentDidMount
函數,組件更新的時候,會執行shouldComponentUpdate
函數,若是返回true
的話,就會一次執行componentWillMount
、render
,componentDidMount
,若是返回false
的話,就不會執行。
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
constructor(props){
super(props)
this.state = {
num:1
}
this.handleClick = this.handleClick.bind(this)
}
// 生命方法--組件渲染完成,只執行一次
componentDidMount(){
console.log('componentDidMount 函數觸發')
}
// 生命方法--避免組件重複或者無心義渲染
shouldComponentUpdate(nextProps,nextState){
if (nextState.num%2) {
return true
}
return false
}
handleClick(){
this.setState({
num:this.state.num+1
})
}
render() {
return (
<div>
<p>{this.state.num}</p>
<button onClick={this.handleClick}>click</button>
</div>
)
}
}
ReactDOM.render(<Counter/>, document.querySelector('#root'))
複製代碼
用戶想提交數據到後臺,表單元素是最經常使用的,一個常見的表單由form
、 input
、 label
等標籤構成,咱們經過onChange()方法
控制value
的值,最終經過state
,讓在的html input
中輸入內容和`React``關聯起來。
import React from 'react';
import ReactDOM from 'react-dom';
class TodoList extends React.Component {
constructor(props){
super(props)
this.state = {
text:''
}
this.handleClick = this.handleClick.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleClick(){
// 若是內部狀態 text有值,則把值清空''
if (this.state.text) {
this.setState({
text:''
})
}
}
handleChange(e){
// 獲取事件元素input的值賦值給內部狀態 text 中
this.setState({
text:e.target.value
})
}
render() {
return (
<div>
{/* 顯示內部狀態 text 的內容*/}
{this.state.text}
{/*input接收到輸入值調用handleChange()方法*/}
<input type="text" value={this.state.text} onChange={this.handleChange}/>
{/*點擊按鈕調用handleClick()方法*/}
<button onClick={this.handleClick}>clear</button>
</div>
)
}
}
ReactDOM.render(<TodoList/>, document.querySelector('#root'))
複製代碼
頁面裏序列化的數據,好比用戶列表,都是一個數組,咱們經過map
函數把數組直接映射爲JSX
,可是咱們直接渲染列表,打開console的時候會看到Each child in an array or iterator should have a unique "key" prop.
報錯。在渲染列表的時候,咱們須要每一個元素都有一個惟一的key屬性,這樣React
在數據變化的時候,知道哪些dom
應該發生變化 尤爲注意key
要惟一,建議每一個字段惟一id,或者使用索引
import React from 'react';
import ReactDOM from 'react-dom';
class TodoList extends React.Component {
constructor(props){
super(props)
// 內部裝填屬性初始化值
this.state = {
todos:['Learn React','Learn Ant-design','Learn Koa'],
text:''
}
this.handleClick = this.handleClick.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleClick(){
if (this.state.text) {
this.setState(state=>({
// 若是內部狀態 text有值,追加到解構的todos數組後
todos:[...state.todos,state.text],
// 若是內部狀態 text有值,則把值清空''
text:''
}))
}
}
handleChange(e){
// 獲取事件元素input的值賦值給內部狀態 text 中
this.setState({
text:e.target.value
})
}
render() {
return (
<div>
{/*input接收到輸入值調用handleChange()方法*/}
<input type="text" value={this.state.text} onChange={this.handleChange}/>
{/*點擊按鈕調用handleClick()方法*/}
<button onClick={this.handleClick}>add</button>
<ul>
{/*map()循環輸出JSX內容給ReactDOM*/}
{this.state.todos.map(v=>{
return <li key={v}>{v}</li>
})}
</ul>
</div>
)
}
}
ReactDOM.render(<TodoList/>, document.querySelector('#root'))
複製代碼
2017年9月27日,Facebook 官方發佈了 React 16.0。相較於以前的 15.x 版本,v16是第一個核心模塊重寫了的版本,而且在異常處理,核心架構和服務端渲染方面都有更新。
render
函數支持返回數組和字符串componentDidCatch
鉤子獲取組件錯誤portals
能夠渲染當前容器dom
以外的節點import React from 'react';
import ReactDOM from 'react-dom';
// 繼承React.Component表示React16是一個組件
class React16 extends React.Component {
// 構造器函數
constructor(props){
super(props)
this.state={hasError:false}
}
// 生命週期函數
componentDidCatch(error, info) {
// 設置內部狀態 hasError爲true
this.setState({ hasError: true })
}
render() {
return (
<div>
{/*? : 是三目運算符*/}
{this.state.hasError ? <div>出錯了</div>:null}
{/*使用組件ClickWithError和FeatureReturnFragments*/}
<ClickWithError />
<FeatureReturnFragments />
</div>
)
}
}
// 繼承React.Component表示ClickWithError是一個組件
class ClickWithError extends React.Component{
constructor(props){
super(props)
this.state = {error:false}
// 綁定handleClick()方法到當前對象上
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
// 觸發調用時設置state.error值爲true
this.setState({
error:true
})
}
render() {
if (this.state.error) {
throw new Error('出錯了!')
}
return <button onClick={this.handleClick}>拋出錯誤</button>
}
}
// 繼承React.Component表示FeatureReturnFragments是一個組件
class FeatureReturnFragments extends React.Component{
render(){
return [
<p key="key1">React很不錯</p>,
"文本1",
<p key="key2">Antd-desing也很贊</p>,
"文本2"
]
}
}
ReactDOM.render(<React16/>, document.querySelector('#root'))
複製代碼
DOM操做成本實在是過高,因此有了在js裏模擬和對比文檔對象模型的方案,JSX裏使用 react
、 createElement
構建虛擬DOM
,每次只要有修改,先對比js裏面的虛擬dom樹
裏的內容。 傳統瀏覽器渲染流程圖
學完了api 的使用,是時候拿起武器開始真刀真槍的開幹了,如圖是實戰的效果演示,具體的代碼分析講解能夠直接在個人github上看到,就不在本文贅述了,我要傳送代碼倉庫===> 項目代碼地址
以下代碼中類的構造方法constructor()
,Test
類繼承了react Component
這個基類,也就繼承這個react
的 基類,纔能有render()
,生命週期等方法
可使用,這也說明爲何函數組件不能使用這些方法的緣由。
super(props)
用來調用基類的構造方法constructor()
, 也將父組件的props
注入給子組件,供子組件讀取(組件 中props
屬性只讀不可寫,state
可寫)。 而 constructor()
用來作一些組件的初始化工做,好比定義this.state
的初始內 容。
import React, { Component } from 'react';
class Test extends Component {
constructor(props) {
super(props);
}
}
複製代碼
此階段分爲componentWillMount
,render
,componentDidMount
三個時期。
DOM
前調用,且只會被調用一次,在這裏面調用this.setState
不會引發組件的從新渲染,也能夠把寫在這裏面的內容改寫到constructor()
中,因此在項目中不多這麼使用。props
和state
(不管二者是重傳遞或重賦值,不管值是否有變化,均可以引發組件從新render
) ,內部return
一個React
元素(描述組件,即UI),該元素不負責組件的實際渲染工做,以後由React
自身根據此元素去渲染出頁面DOM。render
是純函數 (Pure function:函數的返回結果只依賴於它的參數;函數執行過程裏面沒有反作用)
,不能在裏面執行this.setState
等操做,會有改變組件狀態的反作用。DOM
後調用,且只會被調用一次在組件的更新階段中,存在不少生命方法,從上圖能夠很直觀的看到,有 componentWillReceiveProps
, shouldComponentUpdate
,componentWillUpdate
,render
,componentDidUpdate
。
- componentWillReceiveProps(nextProps):此方法只調用於
props
引發的組件更新過程當中,參數nextProps
是父組件傳給當前組件的新props
。但父組件render
方法的調用不能保證重傳給當前組件的props
是有變化的,因此在此方法中根據nextProps
和this.props
來查明重傳 的props
是否改變,以及若是改變了要執行啥,好比根據新的props
調用this.setState
出發當前組件的從新render
- shouldComponentUpdate(nextProps,nextState):此方法經過比較
nextProps
,nextState
及當前組件的this.props
,this.state
,返回true時當前組件將繼續執行更新過程,返回false則當前組件更新中止,以此可用來減小組件的沒必要要渲染,優化組件性能。 這邊也能夠看出,就算componentWillReceiveProps()
中執行了this.setState
,更新了state
,但在render
前 (如shouldComponentUpdate
,componentWillUpdate
),this.state
依然指向更新前的state
,否則nextState
及當前組件的this.state
的對比就一直是true了。- componentWillUpdate(nextProps, nextState):此方法在調用
render
方法前執行,在這邊可執行一些組件更新發生前的工做,通常較少用。- render:
render
方法在上文講過,這邊只是從新調用。- componentDidUpdate(prevProps, prevState):此方法在組件更新後被調用,能夠操做組件更新的
DOM
,prevProps
和prevState
這兩個參數指的是組件更新前的props
和state
在此階段須要先明確下react
組件更新機制。setState
引發的state
更新,或父組件從新render
引發的props
更新,更新後的state
和props
相比較以前的結果,不管是否有變化,都將引發子組件的從新render
。詳細瞭解可看=>這篇文章 形成組件更新有兩類(三種)狀況:
render
引發子組件從新render
的狀況有兩種
直接使用,每當父組件從新
render
致使的重傳props
,子組件都將直接跟着從新渲染,不管props
是否有變化。可通 過shouldComponentUpdate
方法控制優化。class Child extends Component { // 應該使用這個方法,不然不管props是否有變化都將會致使組件跟着從新渲染 shouldComponentUpdate(nextProps){ if(nextProps.someThings === this.props.someThings){ return false } } render() { return <div>{this.props.someThings}</div> } } 複製代碼
componentWillReceiveProps
方法中,將props
轉換成本身的state
class Child extends Component { constructor(props) { super(props); this.state = { someThings: props.someThings }; } componentWillReceiveProps(nextProps) { // 父組件重傳props時就會調用這個方法 this.setState({someThings: nextProps.someThings}); } render() { return <div>{this.state.someThings}</div> } } 複製代碼
根據官網的描述: 在
componentWillReceiveProps
方法中,將props
轉換成本身的state
是由於componentWillReceiveProps
中判斷props
是否變化了,若變化了,this.setState
將引發state
變化,從而引 起render
,此時就不必再作第二次因重傳props
來引發render
了,否則就重複作同樣的渲染了。
setState
,不管state
有沒有變化。能夠經過shouldComponentUpdate
方法控制優化。
shouldComponentUpdate() {
// 組件是否須要更新,返回布爾值,優化點
console.log("5.組件是否應該更新?");
return true;
}
複製代碼
此階段只有一個生命週期方法:componentWillUnmount
此方法在組件被卸載前調用,能夠在這裏執行一些清理工做,好比清楚組件中使用的定時器,清除componentDidMount
中手動建立的DOM元素
等,以免引發內存泄漏。
Fiber
以後就不合適了,由於若是要開啓
async rendering
, 在render函數以前的全部函數,都有可能被執行屢次。
原來(React v16.0前)的生命週期有哪些是在render前執行的呢?
若是開發者開了async rendering
,並且又在以上這些render
前執行的生命週期方法作AJAX請求的話,那AJAX將被 無謂地屢次調用。。。明顯不是咱們指望的結果。並且在componentWillMount
裏發起AJAX,無論多快獲得結果 也趕不上首次render
,並且componentWillMount
在服務器端渲染也會被調用到(固然,也許這是預期的結 果),這樣的IO操做放在componentDidMount
裏更合適。
禁止不能用比勸導開發者不要這樣用的效果更好,因此除了shouldComponentUpdate
,其餘在render
函數以前的 全部函數(componentWillMount
,componentWillReceiveProps
,componentWillUpdate
)都被 getDerivedStateFromProp
s替代。
也就是用一個靜態函數getDerivedStateFromProps
來取代被deprecate的幾個生命週期函數,就是強制開發者在 render
以前只作無反作用的操做,並且能作的操做侷限在根據props
和state
決定新的state
React v16.0剛推出的時候,是增長了一個componentDidCatch
生命週期函數,這只是一個增量式修改,徹底不影 響原有生命週期函數;可是,到了React v16.3,大改動來了,引入了兩個新的生命週期函數。
getDerivedStateFromProps
原本(React v16.3中)是隻在建立和更新(由父組件引起部分),也就是否是不禁 父組件引起,那麼getDerivedStateFromProps
也不會被調用,如自身setState
引起或者forceUpdate
引起。這樣的話理解起來有點亂,在React v16.4中改正了這一點,讓getDerivedStateFromProps
不管是Mounting
仍是Updating
,也不管是由於什麼引發的Updating
,所有都會被調用,具體可看React v16.4 的生命週期圖。
static getDerivedStateFromProps(props, state)
在組件建立時和更新時的render
方法以前調用,它應該返回 一個對象來更新狀態,或者返回null來不更新任何內容。
getSnapshotBeforeUpdate()
被調用於render
以後,能夠讀取但沒法使用DOM
的時候。它使您的組件能夠在可 能更改以前從DOM
捕獲一些信息(例如滾動位置)。今生命週期返回的任何值都將做爲參數傳遞給 componentDidUpdate()。
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
//咱們是否要添加新的 items 到列表?
// 捕捉滾動位置,以便咱們能夠稍後調整滾動.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
//若是咱們有snapshot值, 咱們已經添加了 新的items.
// 調整滾動以致於這些新的items 不會將舊items推出視圖。
// (這邊的snapshot是 getSnapshotBeforeUpdate方法的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
複製代碼