React是Facebook公司研發的一款JS框架(MVC:Model View Controller) 經過數據的改變來影響視圖javascript
React
腳手架React是一款框架:具有本身開發的獨立思想 - 劃分組件開發css
前端工程化開發:html
webpack來完成以上內容(自動化):前端
- 基於路由的spa單頁面開發
腳手架 配置webpack相對複雜,咱們須要安裝不少的包,還須要寫不少相對複雜的配置,這時候腳手架應運而生,用來提高咱們開發的效率vue
vue:vue-cli react:create-react-app(應用)java
一、npm install create-react-app -g
// 安裝到全局能用命令 (npm root -g
查看全局npm安裝目錄) 二、create-react-app demo1
// 項目名稱demo1,名稱只能(/^[a-z0-9_-]$/)node
腳手架生成目錄中的一些內容:react
一、node_module 當前項目中依賴的包 .bin 本地項目中可執行的命令,在package.json的scripts中配置的對應的腳本便可(還有一個是react-scripts命令) 二、public 存放當前項目的頁面(單頁面應用放index.html便可,多頁面根據本身需求放置須要的頁面)webpack
在react中,全部的邏輯都是在JS中完成的(包括頁面機構的建立),若是想給當前的頁面導入一些css樣式後者img圖片等內容,咱們有兩種方式:ios
(1)、在JS中基於ES6 Module模塊規範,使用import引入,這樣webpack在編譯合併JS的時候,會把導入的資源文件等插入到頁面的機構中(絕對不能在js管控的結構中經過相對目錄./ 或者../ 導入資源,由於在webpack編譯的時候,地址就不在是以前的相對地址要用絕對地址)
(2)、若是不想在js中導入(JS中導入的資源最後都是基於webpack編譯),咱們也能夠把資源手動的在html中代入,可是html最後也要基於webpack編譯,導入的地址也不建議寫相對地址,而是使用%PUBLIC_URL%
寫成絕對地址
<link rel="manifest" href="%PUBLIC_URL%/reset.min.css">
複製代碼
三、src 項目結構中最主要的目錄,由於後期全部的JS、路由、組件等都是放到這裏面(包括須要編寫的css或者圖片等) index.js 是入口文件 四、.gitignore git的忽略文件 五、package.json 當前項目的配置清單
"dependencies": {
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-scripts": "1.1.4"
},
複製代碼
基於腳手架生成工程目錄,自動幫咱們安裝了三個模塊:
package.json:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
複製代碼
可執行的腳本
npm run start
或者 yarn start
yarn start
:
- 建立一個默認端口號爲3000 ,協議爲http的web服務
- 按照webpack.config.dev.js把項目編譯
- 打開瀏覽器預覽咱們的項目
- 項目修改時候,自動從新編譯,瀏覽器頁面自動刷新,展現最新的效果
yarn build:
- 基於webpack.config.prod.js 把項目進行編譯打包
- 生成一個build文件夾,存放最後打包的文件>- 生成一個build文件夾,存放最後打包的文件
- 部署上線的時候,只須要把build的內容發佈便可
create-react-app 腳手架爲了讓結構目錄清晰,把安裝的webpack及配置文件都集成在了react-scripts模塊中,放到了node_modules中 可是真實項目中,咱們須要在腳手架默認安裝的基礎上,額外安裝一些咱們須要的模塊,例如react-router-dom
、axios
、還有less
、less-loader
狀況一:若是咱們安裝其餘的組件,可是安裝成功後不須要修改webpack的配置項,此時咱們直接安裝,調取使用就行,好比react-router-dom 、axios等
狀況二:咱們安裝的插件是基於webpack處理的,也就是須要把安裝的模塊配置到webpack中(從新修改webpack配置項了) 一、首先須要把隱藏到node_module中的配置項暴露到項目中
yarn eject
首先會肯定是否確認執行eject,這個操做不可逆轉,一旦暴露出來配置項,就沒法再隱藏了 若是還有未提交到歷史區的內容,須要先提交,而後才能eject 二、再去修改對應的配置項 暴露後,項目目錄中多了兩個文件夾: config:存放的是webpack的配置文件 (1)webpack.config.dev.js
開發環境下的配置項(yarn start) (2)webpack.config.prod.js
生產環境下的配置項(yarn build) scripts:存放的是可執行腳本的JS文件 (1)start.js:yarn start
執行的就是這個JS (2)build.js:yarn build
執行的就是這.個JS
咱們預覽項目的時候,也是基於webpack編譯,把編譯後的內容放到瀏覽器中運行,因此若是項目中使用了less,咱們須要修改webpack配置項,在配置項中加入less的編譯工做,這樣後期預覽項目,首先基於webpack把less編譯爲css
set HTTPS=true && npm start
開啓HTTPS協議模式 set PORT=1111 && npm start
修改默認端口號
一種漸進式框架設計思想,通常框架中都包含了不少內容,這樣致使框架的體積過於臃腫 拖慢加載的速度, 真實項目中,咱們使用一個框架,不必定用到全部的功能,此時咱們應該把框架的功能進行拆分,用戶想用什麼,讓其本身自由組合便可--"漸進式框架"
全家桶:漸進式框架N多部分的組合 vue全家桶:vue、vue-cli、vue-router、vuex、axios、vue-element 、vant、 react全家桶:react、react-react-app、react-dom、react-router、redux、react-redux、axios、ant、dva、saga、mobx
react的核心部分,提供了Component類能夠進行組件開發,提供了鉤子函數(生命週期),全部的生命週期函數都是基於回調函數完成的
raect獨有的,把JSX語法,渲染爲真實DOM(可以放到頁面中展現的機構都叫作真實的DOM)的組件
react框架都是在JS中進行的,而後經過webpack編譯,再放到瀏覽器中編譯
在index.js這個入口文件:
import React from 'react';
import ReactDOM from 'react-dom';
// import ReactDOM,{render} from 'react-dom';
// 從react-dom中導入一個reactDOM,逗號後面的內容是把ReactDOM這個對象進行結構
let data ='zufeng',
root = document.querySelector("#root");
ReactDOM.render(<div id="box">hello world!{data}</div>,root,()=>{
// 回調,通常不用
let oBox = document.querySelector("#box");
console.log(oBox.innerHTML);
});
複製代碼
JSX語法的渲染使用的是ReactDOM.render: ReactDOM.render([jsx],[container],[callback]);
jsx:react獨有的語法, JavaScript+xml(HTML),和咱們以前拼接的字符串相似,都是把html結構代碼和js代碼或者數據混合在一塊兒了,可是他不是字符串 container:容器,咱們想把元素放到頁面中的哪一個容器中 callback:當把容器放到頁面中呈現觸發的回調函數
jsx語法特色:
一、不建議咱們 直接把jsx直接放到body中,而是放在本身建立一個容器中,通常咱們都放在一個id爲root的div中
ReactDOM.render(<section><h2>內容</h2></section>,root)
複製代碼
二、把數據嵌入到jsx中 在JSX中出現的{}
是存放JS的,要求JS代碼執行完成須要有返回結果(JS表達式),不能直接放一個對象數據類型的
let name = 'zhufeng' // 基本數據類型的值
ReactDOM.render(<section><h2{name}</h2></section>,root)
let xx = {name:'xxxx'}
ReactDOM.render(<section><h2{xx}</h2></section>,root) // 報錯
複製代碼
ReactDOM.render(<ul>
{
data.map((item,index)=>{
return <li key={index}>{item.title}</li>
})
}
</ul>,root)
複製代碼
三、循環數組穿件jsx元素,須要給建立的元素設置惟一的key值(當前本次惟一便可) 四、只能出現一個根元素 五、給元素設置樣式類用的是className而不是class 六、style中不能直接寫樣式字符串,須要基於一個樣式對象來遍歷賦值 七、能夠給JSX元素設置屬性: => 屬性值對應大括號中,對象、函數均可以放(也能夠放JS表達式) =>style屬性值必須是對象(不能是字符串)
<li style={{color:'#fff'}}></li>
=>class 用className代替
ReactDOM.render(<h1 id={'box'} className="box" style={{color:'red'}}>我是標題</h1>);
複製代碼
JSX虛擬DOM變爲真實的DOM(react的核心原理之一)
let styleObj = {color:'red'};
ReactDOM.render(<h1 id="titleBox" className='title' style={styleObj.color}></h1>)
複製代碼
JSX渲染機制:JSX->真實DOM
一、基於babel babel-loader babel-present-react-app把JSX語法編譯爲
React.createElement([type],[props],[children])
結構 =>React.createElement中至少有兩參數, type:第一參數的標籤(字符串) props:屬性對象(沒有就是null) 其餘的:都是子元素內容(只要子元素是html,就會變成新的createElement)
var styleObj = { color: 'red' };
ReactDOM.render(React.createElement('h1', { id: 'titleBox', className: 'title', style: styleObj.color }));
複製代碼
二、執行
React.createElement(....)
,建立一個對象(虛擬DOM)
=>key 和ref 都是createElement中的Prop提取出來的 =>props:{ chiledren:存放本身子元素的(沒有子元素就沒有這個屬性),若是有多個子元素,就以數組的形式存儲信息 } 三、
ReactDOM.render(JSX語法最後生成的對象,container,callback)
,基於render方法把生成的對象動態建立爲DOM元素,插入到指定的容器中
無論vue仍是react框架,設計之初都是指望咱們按照「組件 / 模塊管理」 的方式來構建程序的,也就是一個程序劃分一個個的組件來單獨管理
src -> component
:這個文件夾下存放的就是開發用的組件 【優點】 一、有助於多人協助開發 二、組件能夠複用 ....
react 中建立組件有兩種方式:
一、函數式聲明組件
(1)函數返回結果是一個新的JSX(也就是當前組件的JSX結構) (2)props變量存儲的值是一個對象,包含了調取組件時候傳遞的屬性值,不傳遞的話是個空對象
知識點:
createElement遇到一個組件,返回的對象中:type就不是字符串標籤名了,而是一個函數(類),可是屬性仍是存在props中
render渲染的時候,咱們須要作處理:
首先判斷type的類型, 若是是字符串,就建立一個元素標籤, 若是函數或者類,就把函數執行,把props中的每一項(包含children)傳遞給函數 在執行的時候,把函數中return的JSX轉換爲新的對象(經過createElement),把這個對象返回:緊接着
render
按照以往的渲染方式,建立DOM元素,插入到指定的容器中便可
單閉合和雙閉合組件的區別,雙閉合組件中能夠放子孫元素
函數式聲明特色:
一、會把基於createElement解析出來的對象中的props做爲參數傳遞給組件(能夠完成屢次調取組件傳遞不一樣的信息) 二、一旦組件調取成功,返回的
jsx
就會渲染到頁面中,可是後期不經過從新調取組件或者獲取DOM元素操做的方式,很難再把渲染好組件中的內容更改 -->"靜態組件"
//Dialog.js
import React from 'react'; // 每一個組件必須引入,由於須要基於它的create-element把JSX進行解析渲染
export default function Dialog(props) {
//props:調取組件的時候傳遞進來的屬性信息(可能包含className、style、id、可能有children)
let {con, lx = 0, children,style={}} = props,
title = lx === 0 ? '系統提示' : '系統警告';
//children 可能有,可能沒有,多是個值,也多是個數組,每一項多是字符串也多是個對象等,都表明雙閉合組件的子孫元素
return <section style={style}>
<h2>{title}</h2>
<div>{con}</div>
{/*一、把屬性中的子元素,放到組件中的指定位置*/}
{/*{children}*/}
{/*二、也可使用react中專門遍歷children的方法*/}
{children.map(item => item)}
{/*// {React.Children.map(children,item=>item)}*/}
</section>
}
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
// 公共css放index.js中,這樣在其餘組件中也可使用(webpack會把全部的組件最後都編譯到一塊兒,index是主入口)
// import 'static/css/reset.min.css'
import 'bootstrap/dist/css/bootstrap.css' // 須要導入未通過壓縮的文件,不然報錯(真實項目中bootstrap已是過去式了,後期用ant)
import Dialog from "./component/Dialog-bootstrap";
ReactDOM.render(<main>
<Dialog content ='馬少帥很帥'/>
<Dialog type ={2} content='系統錯誤了'/>
<Dialog type = '請登陸' content={
'新的JSX語法'
}>
<div>
<input type="text" className='form-control' placeholder='請輸入用戶名'/><br/>
<input type="password" placeholder='請輸入密碼'/>
</div>
<br/>
<button className='btn btn-success'>登陸</button>
<button className='btn btn-danger'>取消</button>
</Dialog>
</main>,root);
複製代碼
二、繼承component類來建立組件
基於component把JSX轉換爲一個對象,當render渲染這個對象的時候,遇到type是一個函數或者類,不是直接建立元素,而是把方法執行,
特色: 一、調取組件至關於建立類的實例(this),把一些私有的屬性掛載到實例上了,這樣組件內容全部的方法中均可以基於實例獲取這些值(包括:傳遞的屬性和本身管理的狀態) 二、有本身的狀態管理,當狀態改變的時候,react會從新渲染視圖(差別更新:基於DOM-DIFF只把有差別的部分渲染)
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types' // facebook是公司開發的一個插件,基於這個插件咱們能夠給組件傳的屬性設置規則,設置的規則不會影響頁面的渲染,能夠不加
// 可是會在控制檯拋出警告錯誤
class Dialog extends React.Component{
// this.props只讀的,咱們沒法在方法中修改它的值,可是能夠給其設置默認值或者設置一些規則(例如:設置是不是必須傳遞的以及傳遞的值的類型等)
static defaultProps ={
lx:"系統提示"
};
static propTypes = {
// 設置屬性規則,若是傳的不是這個規則的,不影響頁面的渲染,只是會在控制檯拋出警告錯誤
// con: PropTypes.string 傳遞的內容必須是字符串
con:PropTypes.string.isRequired // 傳遞的內容是字符串,並且還必須是傳遞
};
constructor(props){ // props context updater
// props:當render渲染而且把當前類執行建立實例的時候,會把以前JSX解析出來的props對象中的信息(可能有children)傳遞給參數props => "調取組件傳遞的屬性"
// props若是不傳,super也不傳,除了constructor中不能直接使用this.props,其餘聲明周期函數中均可以使用(也就是執行完成constructor,
// react已經幫咱們把傳遞的屬性接收,而且掛載到實例上了)
super(props); // extends繼承,一旦使用了constructor,第一行位置必須設置super執行,至關於React.Component.call(this),也就是call繼承,把父類私有的屬性繼承過來
// this.props:屬性集合
// this.refs:ref集合(非受控組件中用到)
// this.context:上下文
// - 若是super(props); 在繼承父類私有的時候,就把props掛載到this實例上了,這個this只是constructor中的this,不影響原型上的this,寫不寫都行
// - 若是隻寫super() 雖然建立實例的時候把屬性傳遞過來了,可是並無傳遞父組件,也就是沒有把屬性掛載到實例上,使用this.props獲取的結果是undefined
// -
console.log(props);
// AA = 12; 設置私有屬性,可是不符合ES6語法,須要babel-preset-react編譯
// fn=()=>{} 設置一個箭頭函數,可是不符合ES6語法,須要babel-preset-react編譯
}
render(){
// render必須寫還必須有返回的東西
return <section>
<h3>系統提示</h3>
<div>
zhufeng
</div>
</section>
}
}
ReactDOM.render(<div>
<Dialog>
<span>d</span>
</Dialog>
</div>,root);
複製代碼
組件中的屬性(this.props)是調取組件的時候(建立類實例的時候)傳遞給組件的信息,這部分信息是隻讀的(只能獲取不能修改)-> "組件的屬性是隻讀的"
Object.defineProperty(this.props,'cont',{
writable:true
});
複製代碼
用這種方法也改不了
組件部分的總結
建立組件有兩種方式:一是「函數式」,一是「建立類式」
【函數式】
一、操做簡單 二、能實現的功能也很簡單,只是簡單的調取和返回
【建立類式】
一、操做相對複雜一點,可是也能夠實現更爲複雜的業務功能 二、可以使用生命週期函數操做業務 三、函數式能夠理解爲靜態組件(組件中內容調取的時候,就已經固定了,很難再修改),而類這種方式,能夠基於組件內部的狀態來動態更新渲染的內容:
所謂函數組件是靜態組件,和執行普通方法同樣,調取一次組件,就把組件中內容獲取到,若是不從新調取組件,顯示內容是不發生改變的
【需求】實現頁面上時間的走動
一、函數式的
import React from 'react';
import ReactDOM from 'react-dom';
function Clock() {
return <div>
<h3>當前北京時間爲:</h3>
<div style={{color:'red',fontWeight:'bold'}}>{new Date().toLocaleString()}</div>
</div>
}
/*每隔一秒從新調取組件*/
setInterval(()=>{
ReactDOM.render(<Clock/>,root);
},1000)
//適用於組件內容不會再次發生改變的狀況下
複製代碼
二、建立類式
class Clock extends React.Component{
constructor(){
super();
// 初始化組件狀態(都是對象類型):要求咱們在constructor中把後期須要使用的狀態信息所有初始一下(約定俗稱的語法規範)
this.state = {
time:new Date().toLocaleString(),
}
}
componentDidMount(){
//react生命週期函數之一:第一次組件渲染完成後觸發(咱們只須要間隔1000ms把state狀態中的time數據改變,這樣react會幫咱們組件中的部門內容進行渲染)
setInterval(()=>{
// this.state.time = new Date().toLocaleString(); //雖然下面的定時器能夠修改狀態,可是不會通知react從新渲染頁面,這樣不行
//修改組件的狀態
// 一、修改部分狀態:會用咱們傳遞的對象和初始化的state進行匹配,只把咱們傳遞的屬性進行修改,沒有傳遞的依然保留原始的狀態信息(部分修改)
// 二、修改狀態修改完成,會通知react把組件進行從新渲染
this.setState({
time:new Date().toLocaleString(),
},()=>{
/*當通知react把須要從新渲染的JSX元素從新渲染完成後,執行的回調操做(相似於生命週期中的componentDidUpdate)項目中通常使用鉤子函數*/
// 設置回調的緣由是:通知完就直接往下執行,render方法是個異步操做
});
},1000)
}
render(){
return <div> <h3>當前北京時間爲:</h3> <div style={{color:'red',fontWeight:'bold'}}>{this.state.time}</div> </div>
}
}
ReactDOM.render(<Clock/>,root);
複製代碼
React中的組件有兩個很是重要的概念:
一、組件的屬性:[只讀]調取組件的時候傳遞進來的信息 二、組件的狀態:[讀寫]本身在組件中設定和規劃的(只有類聲明式組件纔有狀態的管控你,函數式組件聲明不存在狀態的管理)
組件狀態相似於VUE中的數據驅動:
咱們數據綁定的時候是基於狀態值綁定,當修改組件狀態後,對應的JSX元素也會跟着從新渲染(差別渲染:只把數據改變的部分從新渲染,基於
DOM-DIff
算法完成)
當代前端框架最重要的核心思想就是:「數據操做視圖(視圖影響數據)」,讓咱們告別JQ手動操做DOM的時代,咱們之後只須要改變數據,框架會幫咱們從新渲染視圖,從而減小直接操做DOM(提升性能,也有助於開發效率)
儘可能少操做DOM
在react當中: 一、基於數據驅動(修改狀態數據,react幫助咱們從新渲染視圖)完成的組件叫作「受控組件(受數據管控的組件)」 二、基於ref操做DOM實現視圖更新,叫作「非受控組件」 真實項目中,建議多使用「受控組件」
VUE:MVVM 數據更改,視圖跟着改變,視圖更改,數據也跟着改變(雙向數據綁定) React:MVC 數據更改視圖跟着改變(本來是單向的,可是咱們能夠手動設置爲雙向的)
render(){
let {text} = this.state;
return <section className='panel panel-default'>
<div className='panel-heading'>
<input type="text" className='form-control' value={text} onChange={ev=>{
// 在onChange中修改狀態信息,實現的是視圖改變數據
this.setState({
text:ev.target.value
})
}}/>
</div>
<div className='panel-body'>
{text}
</div>
</section>
}
複製代碼
所謂生命週期函數(鉤子函數)描述一個函數或者組件從建立到銷燬的過程,咱們能夠在過程當中間,基於鉤子函數完成本身的一些操做(例如:在第一次渲染完成作什麼,或者在第二次即將從新渲染以前作什麼等...)
【基本流程】
constructor
: 建立一個組件componentWillMount
: 第一次渲染以前render
:第一次渲染componentDidMount
: 第一次渲染以後 【修改流程】 當組件的狀態數據發生改變(setState)或者傳遞的屬性發生改變(從新調用組件,傳遞不一樣的屬性)都會引起render從新執行渲染(渲染也是差別渲染)shouldComponentUpdate
是否容許組件從新渲染componentWillUpdate
從新渲染以前render
第二次及之後從新渲染componentDidUpdate
從新渲染以後
componentWillReceiveProps
父組件把傳遞給子組件的屬性發生改變後觸發的鉤子函數 屬性改變也會改變子組件從新渲染,觸發鉤子函數
【銷燬】 原有的渲染的不消失,之後不能基於數據改變視圖
componentWillUnmount
卸載組件以前(通常不用)
index.js:
import React from 'react';
import ReactDOM, {render} from 'react-dom';
import PropTypes from 'prop-types';
import 'bootstrap/dist/css/bootstrap.css'
class A extends React.Component {
static defaultProps = {}; // 第一個執行,屬性設置默認值
constructor() {
super();
console.log('1=constructor');
this.state = {
n: 1
}
}
componentWillMount() {
console.log('3=componentWillMount 第一次渲染前', this.refs.HH);
// 在這裏,若是直接setState修改數據(同步的),會把狀態信息改變後,而後render和didMount,若是setState是放到一個異步操做中完成(例如:定時器或者從服務器獲取數據),也是先執行render和did
// 而後再執行這個異步操做修改狀態,緊接着走修改的流程(這樣和放到didMount中沒啥區別),因此咱們通常吧數據請求放到DID中處理
// 真實項目中的數據綁定,第一次組件渲染,咱們都是綁定的默認屬性,第二次纔是從服務器獲取的數據,有些屬性,咱們須要根據數據是否存在,判斷顯示隱藏
}
componentDidMount() {
console.log('4=componentWillMount 第一次渲染後', this.refs.HH);
//真實項目中,這個階段通常作以下處理:
// 一、控制狀態信息更改的操做
// 二、從服務器獲取數據,而後修改狀態信息,完成數據綁定
setInterval(() => {
this.setState({
n: this.state.n + 1
})
}, 5000)
}
shouldComponentUpdate(nextProps, nextState) {
// this.state.n 更新以前的
console.log('5=shouldComponentUpdate 函數返回true(容許),false(不容許)');
// return true
/*在這個鉤子函數中,咱們獲取的state不是最新修改的,而是上一次的state的值
例如:第一次加載完成後,5000ms後,咱們基於setState把n修改成2,可是此處獲取的仍是1呢
可是這個有兩個參數:
nextProps:最新修改的屬性
nextState:最新修改的狀態
*/
if (nextState.n > 3) {
return true
} else {
return false
}
}
componentWillUpdate(nextProps, nextState) {
// this.state.n 也是更新以前的,也有兩個參數存儲最新的信息
console.log('6=componentWillUpdate');
}
componentDidUpdate() {
// this.state.n 更新以後的
// 先render
console.log('8=componentWillUpdate');
}
render() {
console.log('2=render');
return <section ref='HH'>
{this.state.n}
</section>
}
}
ReactDOM.render(<main>
<A></A>
</main>, root);
複製代碼
父組件傳子組件:
基於屬性傳便可(並且傳遞是單方向的:只能把信息給兒子,兒子不能直接把信息做爲屬性傳遞給父親) 後期子組件中的信息須要修改: 可讓父組件傳給子組件的信息發生變化(也就是子組件接收的屬性發生變化,子組件會從新渲染 =>
componentWillReceiveProps
鉤子函數)
子改父:
相似於這種子改父的操做,咱們須要使用一下技巧: 一、把父組件中一個方法做爲屬性傳遞給子組件 二、在子組件中,把基於屬性傳遞進來的方法,在合適的時候執行(相對於在執行父組件中的方法:而這個方法中徹底能夠操做父組件的信息)