根據胡子大哈的文章學習,感謝大胡分享
胡子大哈-react.js第一階段
2017.6.19更新完畢
2017.6.18更新至state
正文開始
畢設搞定,工作搞定,終于。。。其實也不是沒有時間,而是心里不靜,不想寫。
公司要用react,所以,先復習下,開始
一、什么是組件化?
總的來說,各種框架只是為了解決一個問題——開發效率。組件化是一種解決方案。
前端說起來也就三部分——結構,樣式,交互。將某一功能的這三部分抽象出來,提升復用性、可維護性、代碼效率。
那么問題的關鍵是——如何抽象?
- 較廣泛的屬性——>較特殊的屬性
- 輸入不同——>輸出不同
直白點就是類、函數。很明顯,對于前端也就是,用js生成想要的結構,并為其添加功能和樣式。
具體分析
用js生成頁面,再直白點就是——一個字符串形式的dom結構,用js解析、加入功能,然后插入到頁面。
1.結構
那么首先要有一個render函數,返回這個字符串形式的dom結構。(也就是最開始的輸入)
class oneComponent{
render() {
this.el = this.createDOM(`字符串dom結構`);
return this.el;
}
createDOM(str) {
const div = document.createElement('div');
div.innerHTML = str;
return div;
}
}
結構有了,但顯然不夠,還要有交互
2.交互
一個思想——狀態機。
用一些變量來表示狀態,不同的狀態,對應不同的頁面展示。
首先要有一個狀態池,頁面的展示由多個狀態決定,當狀態改變時候,自動重新渲染所有dom。
上邊的思路明顯有一個問題,自動渲染所有dom開銷太大,雖然避免了手工操作dom的各種弊端。解決方案是虛擬dom,其技術細節還有待研究。
首先是狀態池:
class oneComponent{
constructor() {
this.state = {};
}
setState (state) {
this.state = state;
this.render();
}
render () {
this.el = this.createDOM(str);
return this.el;
}
// 功能函數
createDOM(str) {
const div = document.createElement('div');
div.innerHTML = str;
return div;
}
}
有了狀態池,功能就很好做了。比如一個點擊切換內容功能(實際情況的肯定比較復雜了)
class oneComponent{
constructor() {
this.state = {
isShowOK: true;
};
}
setState (state) {
this.state = state;
this.render();
}
render () {
this.el = this.createDOM(`<span>${this.state.isShowOK ? 'ok' : '不ok'}</span>`);
this.el.addEventListener('click', this.clickChangeContent.bind(this), false);
return this.el;
}
// 交互功能
clickChangeContent () {
this.setState ({
isShowOK: !this.state.isShowOK
})
}
// 功能函數
createDOM(str) {
const div = document.createElement('div');
div.innerHTML = str;
return div;
}
}
3.插入頁面
這一部分其實是在使用組件,有兩個階段:
- 第一次使用
第一次使用組件顯然不是自己的事情,而是父組件的事情。 - 狀態改變
每次狀態改變,組件自身都要做到自我更新。
class oneComponent{
constructor() {
this.state = {
isShowOK: true;
};
}
setState (state) {
let oldEl = this.el;
this.state = state;
this.render();
if (this.onStateChange) {
// 留下處理改變的接口
this.onStateChange(oldEl, this.el);
}
}
render () {
this.el = this.createDOM(`<span>${this.state.isShowOK ? 'ok' : '不ok'}</span>`);
this.el.addEventListener('click', this.clickChangeContent.bind(this), false);
return this.el;
}
// 交互功能
clickChangeContent () {
this.setState ({
isShowOK: !this.state.isShowOK
})
}
// 以下功能函數
// 組件初始化
init() {
this.render();
}
// 生成dom str->dom
createDOM(str) {
const div = document.createElement('div');
div.innerHTML = str;
return div;
}
// 使用子組件 這個過程發生在解析父組件自身dom的過程中。遇到了組件的語法,就調用這個函數,并傳入對應組件的實例。
useChildComponent(childComponent) {
let el = childComponent.init();
功能:將el插入到對應的位置。
}
// 自身更新
onStateChange (oldEl, newEl) {
功能:對比前后的dom,哪變化了,就更新哪。
}
}
稍微抽象下
輸入輸出上抽象,組件大概就是上邊這樣,當然有的功能太復雜,就用文字直接簡要說明了下。
雖然看起來還不錯,但是明顯,組件本身也是可以再抽象一下基類。
為了更靈活,可以對基類子類加入props為組件進行配置。這樣可定制性更高了。
class Component {
constructor (props = {}) {
this.props = props;
}
setState (state) {
let oldEl = this.el;
this.state = state;
this.renderDOM();
if (this.onStateChange) {
// 留下處理改變的接口
this.onStateChange(oldEl, this.el);
}
}
renderDOM () {
this.el = this.createDOM(this.render());
此處在解析dom過程中添加事件。類似下邊注釋里邊的。
// this.el.addEventListener('click', this.clickChangeContent.bind(this), false);
return this.el;
}
// 組件初始化
init() {
this.renderDOM();
}
// 生成dom str->dom
createDOM(str) {
const div = document.createElement('div');
div.innerHTML = str;
return div;
}
// 使用子組件 這個過程發生在解析父組件自身dom的過程中。遇到了組件的語法,就調用這個函數,并傳入對應組件的實例。
useChildComponent(childComponent) {
let el = childComponent.init();
功能:將el插入到對應的位置。
}
// 自身更新
onStateChange (oldEl, newEl) {
功能:對比前后的dom,哪變化了,就更新哪。
}
}
class oneComponent extends Component{
constructor() {
super(props);
this.state = {
isShowOK: true;
};
}
render () {
return `<span>${this.state.isShowOK ? 'ok' : '不ok'}</span>`;
}
// 交互功能
clickChangeContent () {
this.setState ({
isShowOK: !this.state.isShowOK
})
}
}
組件化的概念結束,接下來是基礎知識復習
總結一下
組件化設計需要:
- 像上邊這樣的組件類
- 特殊的語法分析器
- 虛擬dom
二、基礎知識
1.雜項
項目生成
使用create-react-app
組件要引入react、
{ Component }
、react-dom
JSX
將js文件中的類似html的語法結構解析成js對象
JSX-->(babel+React.js)-->js對象-->(ReactDOM.render)-->DOM-->插入頁面-
為什么react和react-dom要分離開
- 構造出的js對象不一定非要渲染成dom,還有可能渲染到canvas上,或者手機app上。
- 方便更新組件,使用算法操作這個對象,然后整體更新,減少重排,比較快。
2.組件render方法
寫react就是寫組件,每個組件都必須有一個render方法,返回一個JSX元素。
注意:
- 返回中,最外層只要一個元素
- 在JSX中,class要用className,for要用htmlFor
- JSX中用
{}
插入表達式- 表達式可以是循環,條件,函數,等等
- 表達式中的變量來自組件作用域
3.事件監聽
在JSX中的原生HTML標簽(注意:對組件沒用),為對應的事件接口添加回調即可。
比如onClick,onKeyDown
react事件列表
- event對象
react封裝的對象,屬性與瀏覽器自己的event對象基本一致,保證了瀏覽器兼容性,對外api符合w3c標準。
- 回調中的this
單純指定回調,不綁定this的話,是無法在回調函數中通過this拿到組件實例的。
為什么?
很簡單,回調函數直接將引用傳遞出去,其真正執行階段的作用域是在全局作用域下,也就是說,這是一個在全局環境執行,但是沒有明確指出是window對象調用的函數。
es5規定,在嚴格模式下,不直接用window對象調用函數,其內部this為undefined。react當然用的嚴格模式。
所以,拿不到組件實例,也拿不到window,最后是一個undined。
解決方法:
method.bind(this, arg1, arg2, ...)
4.state
狀態池,就像上一部分說的。
注意:
- 在組件的構造器中定義
- state狀態自動更新要使用
setState()
方法,以便于自動觸發render -
setState()
參數 - 對象——表示組件需要更新的狀態。
- 函數
-
setState()
的使用
- 更新對象放入更新隊列中,在本趟結束后更新。所以,前后數據改變存在邏輯關系的話,在同一個函數中,不能只傳入對象,解決方法如下。
- 使用函數參數解決上邊的問題,參數屬性為上一個setState的結果。這種情況下,react會把setState一趟更新隊列中的狀態合并,并一次渲染。
- 傳入第一個對象參數,并傳入第二個當狀態改變后的回調函數,封裝成一個promise,當成異步來處理。這種情況的話,顯然是更新了三次。
5.props
配置組件,組件是比較抽象的,實例化組件就是一個特殊化的過程,通過props來配置。
父組件調用子組件時候,通過標簽特性傳入。所有的標簽特性都會對應到子組件的props字段上。
可以傳遞任何類型的值。比如函數,對象。
props一旦傳入進來,就不能被改變。這是為了使得組件的形態/行為可以預測。
只有通過父組件重新渲染,才能改變props。
默認props(defaultProps)
static defaultProps = {
likedText: '取消',
unlikedText: '點贊'
}
6.state與props
總結一下:
state組件自己控制自己的狀態池,組件外部不可改變。
props父組件對子組件的初始化狀態池,子組件內部不可改變。
沒有state的組件,叫做無狀態組件,更利于組建維護。因為有了內部狀態,就意味著組件很復雜,復用性降低。
react.js鼓勵無狀態組件。甚至在后來版本引入了函數式組件——不能用state的組件。
const dog = (props) => {
const say = (event) => alert('dog')
return (
<div onClick={say}>dog</div>
)
}
7.渲染列表
渲染列表的一般步驟:
拿到數組-->map遍歷-->返回元素-->羅列渲染
- 注意key值
key,元素的標識,一般是后臺數據的id。
(完)