文章目錄
- 1. 重點提煉
- 2. 表單
- 3. 受控組件
- 3. 1 example02
- 3.2 input
- 3. 3 textarea
- 3. 4 select
- 4. 非受控組件
- 5. 屬性默認值
- 6. props 驗證
- 7. children
1. 重點提煉
- 表單
- 非受控組件和受控組件
- 受控組件:通過react來管理的組件(通過
props
傳遞數據、onChange
事件監聽,來綁定表單控件的一些元素,如input
輸入框,其顯示內容就是通過props
傳遞數據來顯示,同時監聽input
輸入框的一些事件,來達到表單當中的內容和我們React
當中state
綁定的一種效果) - 非受控組件:不通過
react
來管理的控件,而是通過dom
元素自身來維護狀態的控件
- 受控組件:通過react來管理的組件(通過
- ref
- 引用的意思,通過
ref
來綁定組件(原生元素),對組件(元素)進行操作
- 引用的意思,通過
- 非受控組件和受控組件
- props 驗證
- 通過
propTypes
庫來對組件傳入的props
的類型進行驗證(如果使用了TS
,則可以代替propTypes
)
- 通過
- props 默認值
- 類似
props
驗證,它的作用是爲傳入的props
進行默認值的設置
- 類似
- children
- 組件包含的內容 -
dialog
- 與
vue
以及 原生html
中的slot
(插槽)類似
- 組件包含的內容 -
2. 表單
在 React 裏,HTML 表單元素的工作方式和其他的 DOM 元素有些不同。
一般來說,表單以及表單中的(受用戶控制,可交互,即交互式元素)控件(如:input
、select
……)是頁面中與 JavaScript 打交道最多的元素了。雖然我們可以通過 ref 的形式去操作它們,但是這樣會比較麻煩,React.js 爲我們提供了一個更好的方式把 React.js 中的數據以及邏輯與表單控件關聯起來。
2. 1 example01
利用腳手架工具構建,我就不再細說,如有問題,請參考小迪之前的React文章。
2. 1. 1 example01-1
舉一個例子:如何操作React的表單
react-Novice03\app\src\components\FormDemo.js
import React from 'react';
import FormDemo from "./components/FormDemo";
function App() {
return (
<div className="App">
<FormDemo/>
</div>
);
}
export default App;
react-Novice03\app\src\App.js
import React from 'react';
import FormDemo from "./components/FormDemo";
function App() {
return (
<div className="App">
<FormDemo/>
</div>
);
}
export default App;
看似以上顯示很正常,但我們把數據傳遞到表單裏面了,然後在頁面上往input
框裏輸入值做修改。我們發現在頁面上怎麼修改都修改不成功!這是爲什麼呢?
因爲它是非受控組件,下面我們模擬一下這個特性:非受控特性(默認情況下)。
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.01-1
Branch:branch2
commit description:v1.01-1-example01-1 (操作React的表單)
tag:v1.01-1
2. 1. 2 example01-2
UnControl
組件其實就相當於input
標籤。我們往input
裏輸入值,就相當於從外界傳入了value
屬性,假設我們在外界想修改App
中的this.state
,實際就改不了(上節課知識點)。那改不了的原因是什麼呢?我們再往下看。
react-Novice03\app\src\components\UnControl.js
import React from 'react';
export default class UnControl extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
我的值是:{this.props.value}
</div>
);
}
}
react-Novice03\app\src\App.js
import React from 'react';
import FormDemo from "./components/FormDemo";
import UnControl from "./components/UnControl";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
}
}
render() {
return (
<div className="App">
<FormDemo />
<hr/>
<UnControl value={this.state.v1} />
</div>
)
}
}
export default App;
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.01-2
Branch:branch2
commit description:v1.01-2-example01-2 (模擬input非受控組件)
tag:v1.01-2
2. 1. 3 example01-3
假設我們需要修改父級這個數據,我們添加事件試試:
react-Novice03\app\src\components\FormDemo.js
import React from 'react';
export default class FormDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
};
}
render() {
return(
<div>
<h2>表單</h2>
<hr/>
<input type="text" value={this.state.v1}/>
<button onClick={ () => {
this.setState({
v1: this.state.v1 + 1
})
}}>按鈕</button>
</div>
);
}
}
這樣是可以修改的!但是我們卻不能直接修改input
框裏的值!
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.01-3
Branch:branch2
commit description:v1.01-3-example01-3 (事件修改state控制表單數據)
tag:v1.01-3
那麼什麼叫非受控呢?
其實表單input
內部會有一個狀態(私有數據),對外暴露的是一個value
的props
,但是對外接收到props
以後,會賦值給內部這個私有狀態(私有數據),而內部(非受控組件)沒有提供任何方法,能去修改它的值。所以這個值我們怎麼也修改不了。
其實對比理解,受控組件就是外部的用戶行爲可以控制組件的變化,而非受控組件則正好相反。
3. 受控組件
受控組件
: 用 props 傳入數據的話,組件可以被認爲是受控(因爲組件被父級傳入的 props 控制)
非受控組件
: 數據只保存在組件內部的 state 的話,是非受控組件(因爲外部沒辦法直接控制 state)
廣義來說,頁面中的任意元素都是一個獨立的組件,表單控件也是,它們內部也會維護屬於自己的狀態(如:value,selected,checked……
),當然這些狀態是由原生實現的,而非 React.js 來控制的,但是有的時候我們希望通過 React.js 來管理和維護表單控件的狀態,我們把這種控件(控件)稱爲: 受控組件, 針對不同的組件,狀態的維護方式也有所差異。
input
textarea
select
通過 state 來控制組件狀態
- 創建 state 與組件的某個狀態進行綁定
- 監聽組件某些事件來更新 state
反着理解上例,其實對於內部數據,外部可通過props
去影響外部的數據(input
組件值的變化),但是這個時候正好有一個相反的東西,是它內部數據的變化:
3. 1 example02
下面我們仔細探究一下非受控組件
3. 1. 1 example02-1
如下:假如內部的數據不綁定寫死!我們還是修改不了input
框裏的數據。
render() {
return(
<div>
<h2>表單</h2>
<hr/>
<input type="text" value="1"/>
<button onClick={() => {
this.setState({
v1: this.state.v1+1
})
}}>按鈕</button>
</div>
);
}
相信不用演示,也可以猜到,如手動修改input框中的1,肯定是沒有任何反應的。因此像input
內部都會維護自己的狀態,除非你直接修改它的value
屬性,導致它的頁面重新渲染。它內部的數據是內部自己維護的,我們從外部(用戶行爲)是無法操控的。
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.02-1
Branch:branch2
commit description:v1.02-1-example02-1 (指定input固定值value仍不爲所動的愛情)
tag:v1.02-1
3. 1. 2 example02-2
我們再看一個例子,把state屬性值,放到input
和button
中間。
我們把外部的值傳入表單的內部,內部則會維護這個狀態,即內部會有一個數據記錄了我們傳進去的初值。緊接着,我們希望表單當中的數據,能跟接受用戶控制並且與外界的state
進行關聯。
我們可以通過一種方式將其變爲受控型組件,是其與state
相互影響。希望通過 React.js 來管理和維護表單控件的狀態,我們把這種控件(控件)稱爲: 受控組件
希望input
中的value
能夠隨着state
數據的變化而變化,即兩者能夠互相影響,該如何去做呢?
雖然非受控組件不受用戶行爲控制,但是當發生用戶行爲時,會觸發onChange
事件(注意這是原生的事件)。當這個事件觸發,我們就可以搞事情了。
react-Novice03\app\src\components\FormDemo.js
import React from 'react';
export default class FormDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
};
this.changeV1 = this.changeV1.bind(this);
}
changeV1 (e) {
console.log('...', e.target.value);
}
render() {
return(
<div>
<h2>表單</h2>
<hr/>
<input type="text" value={this.state.v1} onChange={this.changeV1}/>
{this.state.v1}
<button onClick={ () => {
this.setState({
v1: this.state.v1 + 1
})
}}>按鈕</button>
</div>
);
}
}
你一定會感覺很奇怪,小迪拼命往裏input
裏輸數據,爲啥表單顯示的值不變,而觸發事件中打印的log
中這個 e.target.value
會發生變化呢?
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.02-2
Branch:branch2
commit description:v1.02-2-example02-2 (input裏輸數據,爲啥表單顯示的值不變,而觸發事件中打印的log卻顯示呢?)
tag:v1.02-2
3. 1. 3 example02-3
我們是否掌握一個概念,叫DOM
屬性,瞭解attribute
和property
嗎?及它兩者之間的差異性。我們探究一下這裏的原理。
獲取一下input
的value
屬性的方式:兩中方式
js property
(js
對象屬性)html attribute
(html
屬性)
attribute和property.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="text" value="1" />
<button>按鈕</button>
<script>
let input = document.querySelector('input');
let button = document.querySelector('button');
button.onclick = function() {
console.log(input.value);
console.log(input.getAttribute('value'));
}
</script>
</body>
</html>
起初我們打印的log
兩個屬性值一致,後來我們改變input
框的值,發現input.getAttribute('value')
就沒有同步了,這是爲啥呢?
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.02-3
Branch:branch2
commit description:v1.02-3-example02-3(DOM屬性,對attribute和property深入探究)
tag:v1.02-3
直接獲取我們設置value
值,我們看不出去這兩種的差異!但是假若我們在瀏覽器中手動修改value
值,就發現了兩者的差異性了。這是爲什麼呢?
因此以上兩種屬性是有差異性的,第一個是js
的對象屬性,第二個是html
的屬性,但是英文單詞(attribute和property
)不一樣!它們是有映射關係的。
3. 1. 3. 1 attribute和property深入探究
在dom
解析的時候,我們經常聽到dom
樹,dom
樹是什麼東西,和虛擬dom
是一樣的,瀏覽器會在解析我們的html文檔的時候,如果我們把html文檔當作字符串去操作的話,會很麻煩的,因此做了一種關係叫對象映射,它就會分析html中的每一個元素的結構,然後把不同的元素轉成js
中對象去表示。就相當於我們想操作界面元素的話,直接去操作對應的對象就行了。因爲它們在解析過程中會有對應關係,即映射關係,然後它內部又會做一件事,當我們去操作js
當中對象的時候,它就會影響(重新渲染/重繪)我們的html。但是就如上面的代碼,我們定義input
對象,並不完全等於html上的input
標籤。
這裏發現操作瀏覽器上的input
的value
值,實際操作的是js
對象,而未對html標籤的value
值產生任何的影響!
但是我們還會發現一個現象,我們通過瀏覽器的F12工具欄中的Elements
刪除input
標籤,但是我們的input
的js
對象還是可以運行,並且也可以輸出input.value
。因爲js
中的這個對象是沒有消除的,因爲它是將html標籤作爲參考而生成的對象,並且它不等同於html元素標籤。
因此迴歸正題,我們在瀏覽器裏看到的value
只是html標籤的中value
值,剛刷新頁面的時候打印出來,兩者的值一樣。但是當我們修改了頁面的input
框,只是修改了映射出來的js
對象的value值,並沒有改變標籤裏的value
屬性。
我們還有一種做法,直接通過F12
工具欄中的Elements
去修改input
的value值,當頁面元素髮生改變了以後,即當頁面更新的時候,頁面會動態獲取,重新映射導致兩個值一致了!
3. 1. 3. 2 example02-2小結
回到我們正文的React。
因此我們輸入內容以後,onChange
事件處理函數中的e.target.value
仍然可獲取。我們當前的value值是根據this.state.v1
渲染出來的的,雖然我們在內部把這個原生js
的元素的對象value
值改了,就相當於改了input.value
,但是並沒有反饋到當前this.state
上。
3. 1. 4 example02-4
無法修改當前this.state.v1
的值,因此我們需要在觸發的事件中做處理!
react-Novice03\app\src\components\FormDemo.js
import React from 'react';
export default class FormDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
};
this.changeV1 = this.changeV1.bind(this);
}
changeV1 (e) {
this.setState({
v1: e.target.value
})
}
render() {
return(
<div>
<h2>表單</h2>
<hr/>
<input type="text" value={this.state.v1} onChange={this.changeV1}/>
{this.state.v1}
<button onClick={ () => {
this.setState({
v1: this.state.v1 + 1
})
}}>按鈕</button>
</div>
);
}
}
組件這就受控了。
因此當我們操作input
改變其value
的時候,它內部會自己修改value
值了,但是又不受外界的控制(不影響外層的(props
)頁面顯示),我們的value
根據this.state.v1
來渲染,但內部value
的變化又不會影響this.state.v1
,所以導致最終input
框的值未發生更改。要想改,就需要傳入一個onChange
事件即可。
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.02-4
Branch:branch2
commit description:v1.02-4-example02-4(成功將非受控組轉化爲受控組件)
tag:v1.02-4
3. 1. 4. 1 小結
其實默認情況下,把input
標籤當成一個組件就可以了,組件內部數據狀態(state
)的變化,它不會主動影響外界傳進來的(原生js
)props
的,導致我們最終看到的界面就是,input
標籤的value
根據this.state.v1
進行渲染,即頁面上input框的值根據this.state
進行渲染,但是內部的狀態變化又不會更改從外部傳入的this.state.v1
,所以看到效果就是我們輸入的值不會顯示在input
框上。那麼我們想利用React的外部props
去控制input
的數據,就只能採取事件的形式了。
3. 1. 4. 2 應用場景:控制和管理用戶合法輸入
修改需求:用戶不管輸入什麼,都需要轉成大寫!我們只需要控制好數據,再去渲染即可。
changeV1(e) {
console.log('...', e.target.value);
this.setState({
v1: e.target.value.toUpperCase()
})
}
這裏就不放案例代碼,大家自己去整吧!
3. 1. 5 example02-5
迴歸主題,我們這裏講的是非受控組件,input
標籤value
值的變化,只能通過內部去改變,但是在外部用戶輸入等外部行爲,是不能改變改value值的!而我們在onChange
事件中,改變state
的時候,會重新渲染(調用setState
方法的緣故)input
的。其實就是我們第一次將v1
傳給非受控組件時頁面渲染,會解析並同步js和html
的屬性值,但後面我們使js
的value
變了,除非你讓其重新渲染,否則標籤的value
值肯定沒有變化的,也不可能改變v1
的值,因爲我們無法從外界直接訪問state
屬性的。
並且我們在起初寫這個代碼的時候,會發現瀏覽器報錯!
import React from 'react';
export default class FormDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
};
}
render() {
return(
<div>
<h2>表單</h2>
<hr/>
<input type="text" value={this.state.v1}/>
{this.state.v1}
<button onClick={() => {
this.setState({
v1: this.state.v1+1
})
}}>按鈕</button>
</div>
);
}
}
有一個有問題的prop
類型,你提供了一個value
屬性給表單控件,但是又沒提供一個onChange
的處理函數,這個時候就出問題了。
因此如果你希望處理當前問題,請提供onChange
函數。
但是假若我們後續不需要改這個input
值(只涉及一個初值即可),怎麼解決這個報錯問題呢?可以用defaultValue
屬性。
render() {
return(
<div>
<h2>表單</h2>
<hr/>
<input type="text" defaultValue={this.state.v1}/>
{/*<input type="text" value={this.state.v1} onChange={this.changeV1} />*/}
{this.state.v1}
<button onClick={() => {
this.setState({
v1: this.state.v1+1
})
}}>按鈕</button>
</div>
);
}
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.02-5
Branch:branch2
commit description:v1.02-5-example02-5(假若我們後續不需要改這個input值(只涉及一個初值即可),可以用defaultValue屬性解決報錯問題。)
tag:v1.02-5
同樣的後面講的textarea
和select
標籤也是同樣的原理。
3.2 input
class ControlledComponent extends React.Component {
constructor(args) {
super(args);
this.state = {
v1: '1'
};
this.changeValue = this.changeValue.bind(this);
}
changeValue({target:{value:v1}}) {
this.setState({
v1
});
}
render() {
return(
<div>
<input type="text" value={this.state.v1} onChange={this.changeValue} />
</div>
);
}
}
3. 2. 1 通過受控組件,可以更加便捷的操控組件交互
...
changeValue({target:{value}}) {
this.setState({
v1: value.toUpperCase()
});
}
...
3. 3 textarea
textarea 與 input 類似,但是需要注意的是: 使用 value ,而不是 內容(innerHTML)
// 正確
<textarea value={this.state.v2} onChange={this.changeValue2} cols="30" rows="10"></textarea>
// 錯誤
<textarea onChange={this.changeValue2} cols="30" rows="10">{this.state.v2}</textarea>
3. 4 select
select 在 React.js 中也做了一些處理,不在是通過 selected 屬性來表示選中元素,而是通過 select 標籤的 value 屬性
<select value={this.state.v3} onChange={this.changeValue3}>
<option value="html">html</option>
<option value="css">css</option>
<option value="javascript">javascript</option>
</select>
3. 4. 1 多選
我們還可以設置多選 select,對應的 value 就是一個數組。
option
的值存在state
的數組中,就會被選中。
...
this.state = {
v4: ['html', 'javascript']
}
...
...
changeValue4({target:{options}}) {
this.setState({
v4: [...options].filter(o=>o.selected).map(o=>o.value)
});
}
...
...
<select value={this.state.v4} onChange={this.changeValue4} multiple>
<option value="html">html</option>
<option value="css">css</option>
<option value="javascript">javascript</option>
</select>
...
3. 4. 2 單選
radio 和下面的 checkbox 需要注意的是,受控的屬性不在是 value ,而是 checked
...
this.state = {
v5: '女',
v6: ['前端', '後端'],
}
...
changeValue5(e) {
this.setState({
v5: e.target.value
});
}
changeValue6({target:{value}}) {
let {v6} = this.state;
if (v6.includes(value)) {
v6 = v6.filter(v=>v!==value);
} else {
v6.push(value)
}
this.setState({
v6
});
}
...
...
<label><input name="gender" type="radio" value="男" checked={this.state.v5==='男'} onChange={this.changeValue5} />男</label>
<label><input name="gender" type="radio" value="女" checked={this.state.v5==='女'} onChange={this.changeValue5} />女</label>
<label><input name="interest" type="checkbox" value="前端" checked={this.state.v6.includes('前端')} onChange={this.changeValue6} />前端</label>
<label><input name="interest" type="checkbox" value="後端" checked={this.state.v6.includes('後端')} onChange={this.changeValue6} />後端</label>
...
4. 非受控組件
話又說回來,通過上面的學習,我們知道,每個受控組件,且不同的類型的受控組件它能控制的狀態只有那麼一些:value、checked,但是實際上一個組件的狀態遠遠不止這些,比如 input 的焦點、禁用、只讀 等,都是組件的狀態,如果每一個狀態都通過上面的方式來管理,就會特別的麻煩了。這個時候,我們就需要用其他方式來處理了:DOM
4. 1 example03
但是利用原生dom
必然會有弊端,我們舉個例子看看。
需求:點擊按鈕,動態獲取p標籤內容高度。
4. 1. 1 example03-1
React內是沒有方法可以用的,我們還是得進行dom
操作。因此有了框架之後,就避免所有的dom
操作是不現實的,因此如果開發組件和庫的話,甚至開發過程中稍微頂層一點,原生dom
操作是無法避免的。
react-Novice03\app\src\components\RefDemo.js
import React from 'react';
export default class UnControl extends React.Component {
constructor(props) {
super(props);
this.state = {
content: 'Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師'
};
this.getHeight = this.getHeight.bind(this);
}
getHeight() {
let p = document.querySelector('p');
console.log(p);
}
render() {
return(
<div>
<p style={{background: 'red', color: 'white'}}>{this.state.content}</p>
<button onClick={this.getHeight}>按鈕</button>
</div>
);
}
}
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.03-1
Branch:branch2
commit description:v1.03-1-example03-1(需求:點擊按鈕,動態獲取p標籤內容高度。dom操作。)
tag:v1.03-1
4. 1. 2 example03-2
以上例子,當我們點擊按鈕的時候,p標籤已經出現在頁面上了(已經渲染出來了)。但有時候組件還沒渲染出來,我們就獲取它的dom
節點。如在構造函數中:
constructor(props) {
super(props);
this.state = {
content: 'Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師'
};
this.getHeight = this.getHeight.bind(this);
let p = document.querySelector('p');
console.log(p);
}
此時獲取就是null
,因爲在constructor
執行的時候,render
方法還未執行。
因此這種dom
獲取節點的方式,其實是不推薦的。
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.03-2
Branch:branch2
commit description:v1.03-2-example03-2(需求:點擊按鈕,動態獲取p標籤內容高度。dom操作弊端。)
tag:v1.03-2
4. 2 Refs & DOM
React.js 提供了多種方式來獲取 DOM 元素
- 回調 Refs
- React.createRef()
4. 2. 1 ref 屬性
無論是 回調 Refs 還是 React.createRef(),都需要通過一個屬性 ref 來進行設置
<input ref={?} />
4. 2. 1. 1 回調 Refs
這種方式,我們在前面已經使用過了
class UnControlledComponent extends React.Component {
constructor(props) {
super(props);
this.selectURL = this.selectURL.bind(this);
this.getElementInfo = this.getElementInfo.bind(this);
}
selectURL() {
this.refInput.select();
}
getElementInfo() {
this.refDiv.getBoundingClientRect()
}
render() {
return (
<input ref={el => this.refInput = el} type="text" value="http://www.baidu.com" />
<button onClick={this.selectURL}>點擊複製</button>
<hr/>
<button onClick={this.getElementInfo}>獲取元素信息</button>
<div ref={el => this.refDiv = el} style={{width: '100px', height:'100px',background:'red'}}></div>
)
}
}
4. 2. 1. 1. 1 example04
ref
可以傳回調函數,其實它本身類似於回調函數。
如下代碼,當React解析下面的p標籤的時候,它發現這裏有一個ref
屬性,並且其值是一個函數,那這個函數就會執行了。並且這個回調函數會接受一個參數,我們打印可以得到,它實際是就是這個元素。那如何實現動態獲取高度呢?我們不能在這裏獲取,因爲是點擊按鈕以後才獲取高度的。我們可以在對象裏定一個自定義屬性,當ref
屬性被解析後,我們就賦值給該屬性。當我們點擊的時候,直接獲取該屬性下的高度即可。這樣就免去了每次都需要獲取dom
節點了,因爲在對象中原生dom
操作,在構造函數中獲取到全局對象必然是null
(剛剛講了原因),所以用原生的話,每個函數要用到該節點都得重新獲取,會顯得非常麻煩和冗餘。並且原生中,你萬一不確定是否解析完成,就獲取成null
了。而用ref
,必然是解析過後才能得到的。
react-Novice03\app\src\components\RefDemo.js
import React from 'react';
export default class UnControl extends React.Component {
constructor(props) {
super(props);
this.state = {
content: 'Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師'
};
this.getHeight = this.getHeight.bind(this);
this.refE1 = null;
}
getHeight() {
console.log(this.refEl.offsetHeight)
}
render() {
return(
<div>
<button onClick={this.getHeight}>按鈕</button>
<p ref={el => {
console.log('...', el)
{
this.refEl = el;
}
}} style={{background: 'red', color: 'white'}}>{this.state.content}</p>
</div>
);
}
}
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.04
Branch:branch2
commit description:v1.04-example04(需求:點擊按鈕,動態獲取p標籤內容高度。回調Refs實現。)
tag:v1.04
4. 2. 1. 2 React.createRef()
該方法返回一個 ref 對象,在 jsx 通過 ref 屬性綁定該對象,該對象下的 current 屬性就指向了綁定的元素或組件對象
class ChildComponent extends React.Component {
constructor(props) {
super(props);
}
hello() {
console.log('ChildComponent');
}
render() {
return(
<div>
<h2>ChildComponent</h2>
</div>
);
}
}
class UnControlledComponent extends React.Component {
constructor(props) {
super(props);
this.selectURL = this.selectURL.bind(this);
this.getElementInfo = this.getElementInfo.bind(this);
this.refInput = React.createRef();
this.refDiv = React.createRef();
this.refChild = React.createRef();
}
selectURL() {
this.refInput.current.select();
}
getElementInfo() {
this.refDiv.current.getBoundingClientRect()
}
getElementInfo() {
this.refChild.current;
}
render() {
return (
<input ref={this.refInput} type="text" value="http://kaikeba.com" />
<button onClick={this.selectURL}>點擊複製</button>
<hr/>
<button onClick={this.getElementInfo}>獲取元素信息</button>
<div ref={this.refDiv} style={{width: '100px', height:'100px',background:'red'}}></div>
<hr/>
<ChildComponent ref={this.refChild} />
<button onClick={this.getReactComponent}>獲取 React 實例對象</button>
)
}
}
4. 2. 1. 2. 1 example05
import React from 'react';
export default class UnControl extends React.Component {
constructor(props) {
super(props);
this.state = {
content: 'Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師Web前端工程師'
};
this.getHeight = this.getHeight.bind(this);
this.refE1 = null;
// 自動生成幫助賦值節點的函數,但是並不是直接就是這個元素標籤,得到是一個對象,對象內的current纔是真正的標籤對象
this.refEl2 = React.createRef();
}
getHeight() {
console.log(this.refEl2);
console.log(this.refEl2.current.offsetHeight);
}
render() {
return(
<div>
<button onClick={this.getHeight}>按鈕</button>
{/*<p ref={el => {*/}
{/* console.log('...', el)*/}
{/* {*/}
{/* this.refEl = el;*/}
{/* }*/}
{/*}} style={{background: 'red', color: 'white'}}>{this.state.content}</p>*/}
<p ref={this.refEl2} style={{background: 'red', color: 'white'}}>{this.state.content}</p>
</div>
);
}
}
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.05
Branch:branch2
commit description:v1.05-example05(需求:點擊按鈕,動態獲取p標籤內容高度。React.createRef()實現。)
tag:v1.05
4. 3 建議
- 儘量避免從 props 中派生 state
- 儘量使用 props,避免使用 state
5. 屬性默認值
5. 1 defaultProps 靜態屬性
defaultProps 可以爲 Class 組件添加默認 props。這一般用於 props 未賦值,但又不能爲 null 的情況
注意:defaultProps 是 Class 的屬性,也就是靜態屬性,不是組件實例對象的屬性
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<h2>MyComponent - {this.props.max}</h2>
</div>
);
}
}
MyComponent.defaultProps = {
max: 10
}
ReactDOM.render(
<MyComponent />,
document.getElementById('app')
);
5. 1. 1 example06
5. 1. 1. 1 example06-1
引例
react-Novice03\app\src\components\PropsDefaultValueDemo.js
import React from 'react';
export default class PropsDefaultValueDemo extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<h2>值 - {this.props.max}</h2>
</div>
);
}
}
react-Novice03\app\src\App.js
import React from 'react';
import FormDemo from "./components/FormDemo";
import UnControl from "./components/UnControl";
import RefDemo from "./components/RefDemo";
import PropsDefaultValueDemo from "./components/PropsDefaultValueDemo";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
}
}
render() {
return (
<div className="App">
{/*<FormDemo />*/}
{/*<hr/>*/}
{/*<UnControl value={this.state.v1} />*/}
{/*<RefDemo />*/}
<PropsDefaultValueDemo max={1} />
</div>
)
}
}
export default App;
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.06-1
Branch:branch2
commit description:v1.06-1-example06-1(屬性默認值-引例)
tag:v1.06-1
5. 1. 1. 2 example06-2
組件其實就是函數,有的時候我們需要在組件內部控制外界傳入的參數是否合法。並且有的時候沒有傳值,我們也希望其可以顯示一個默認值。
可以用邏輯或
react-Novice03\app\src\components\PropsDefaultValueDemo.js
import React from 'react';
export default class PropsDefaultValueDemo extends React.Component {
constructor(props) {
super(props);
}
render() {
let max = this.props.max || 1;
return(
<div>
<h2>值 - {max}</h2>
</div>
);
}
}
react-Novice03\app\src\App.js
import React from 'react';
import FormDemo from "./components/FormDemo";
import UnControl from "./components/UnControl";
import RefDemo from "./components/RefDemo";
import PropsDefaultValueDemo from "./components/PropsDefaultValueDemo";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
}
}
render() {
return (
<div className="App">
{/*<FormDemo />*/}
{/*<hr/>*/}
{/*<UnControl value={this.state.v1} />*/}
{/*<RefDemo />*/}
<PropsDefaultValueDemo />
</div>
)
}
}
export default App;
<PropsDefaultValueDemo max={1000} />
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.06-2
Branch:branch2
commit description:v1.06-2-example06-2(屬性默認值-用邏輯
或
)tag:v1.06-2
5. 1. 1. 3 example06-3
react-Novice03\app\src\components\PropsDefaultValueDemo.js
import React from 'react';
export default class PropsDefaultValueDemo extends React.Component {
/**
* 給當前組件的props設置默認值
*/
static defaultProps = {
max: 10
};
constructor(props) {
super(props);
}
render() {
return(
<div>
<h2>值 - {this.props.max}</h2>
</div>
);
}
}
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.06-2
Branch:branch2
commit description:v1.06-3-example06-3(屬性默認值-defaultProps)
tag:v1.06-3
5. 1. 2 基於 static 的寫法
class MyComponent extends React.Component {
static defaultProps = {
max: 10
}
constructor(props) {
super(props);
}
render() {
return(
<div>
<h2>MyComponent - {this.props.max}</h2>
</div>
);
}
}
ReactDOM.render(
<MyComponent />,
document.getElementById('app')
);
5. 2 非受控組件默認值
有的時候,我們希望給一個非受控組件一個初始值,但是又不希望它後續通過 React.js 來綁定更新,這個時候我們就可以通過 defaultValue 或者 defaultChecked 來設置非受控組件的默認值
5. 2. 1 defaultValue 屬性
<input type="text" defaultValue={this.state.v1} />
5. 2. 2 defaultChecked 屬性
<input type="checkbox" defaultChecked={this.state.v2} />
<input type="checkbox" defaultChecked={this.state.v3} />
6. props 驗證
隨着應用的不斷增長,也是爲了使程序設計更加嚴謹,我們通常需要對數據的類型(值)進行一些必要的驗證,React.js 提供了一個驗證庫:prop-types
主要是對傳入對props
參數對數據類型進行安全(合法性)驗證,主要進行類型驗證。不過還是推薦使用typescript
做驗證,它的功能更爲強大,官方也是推薦使用ts
。但兩者是有差異的,ts
是在編譯過程中作類型檢測,prop-types是針對代碼層面的,還會附件一些功能。
6. 1 prop-types
prop-types 是一個獨立的庫,需要安裝
https://www.npmjs.com/package/prop-types
6. 1. 1 安裝
npm i -S prop-types
6. 2 使用
import PropTypes from 'prop-types';
它的使用並不複雜,與 defaultProps 類似,我們在組件類下添加一個靜態屬性 propTypes ,它的值也是一個對象,key 是要驗證的屬性名稱,value 是驗證規則
MyComponent.propTypes = {
// You can declare that a prop is a specific JS primitive. By default, these
// are all optional.(提供的驗證函數如下)
optionalArray: PropTypes.array, // 是不是數組
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func, // 是不是函數
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object, // 是不是對象
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// Anything that can be rendered: numbers, strings, elements or an array
// (or fragment) containing these types.
optionalNode: PropTypes.node, // 是不是node節點
// A React element (ie. <MyComponent />).
optionalElement: PropTypes.element, // 是不是元素
// A React element type (ie. MyComponent).
optionalElementType: PropTypes.elementType,
// You can also declare that a prop is an instance of a class. This uses
// JS's instanceof operator.
optionalMessage: PropTypes.instanceOf(Message), // 是不是某個對象
// You can ensure that your prop is limited to specific values by treating
// it as an enum.
optionalEnum: PropTypes.oneOf(['News', 'Photos']), // 值是否是該數組中的其中之一
// An object that could be one of many types
optionalUnion: PropTypes.oneOfType([ // 當前類型是否是其中之一
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// An array of a certain type 是否包含其中
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// An object with property values of a certain type 是否包含其中
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// You can chain any of the above with `isRequired` to make sure a warning
// is shown if the prop isn't provided.
// An object taking on a particular shape
optionalObjectWithShape: PropTypes.shape({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
requiredFunc: PropTypes.func.isRequired, // 代表必傳參,不能省略
// A value of any data type
requiredAny: PropTypes.any.isRequired,
// You can also specify a custom validator. It should return an Error
// object if the validation fails. Don't `console.warn` or throw, as this
// won't work inside `oneOfType`.(自定義規則<常用>)
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error( // 錯誤提示可以自己編寫
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// You can also supply a custom validator to `arrayOf` and `objectOf`.
// It should return an Error object if the validation fails. The validator
// will be called for each key in the array or object. The first two
// arguments of the validator are the array or object itself, and the
// current item's key.
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
6. 2. 1 example07
使用演示。
6. 2. 1. 1 example07-01
react-Novice03\app\src\components\PropTypesDemo.js
import React from 'react';
import PropTypes from 'prop-types';
export default class PropTypesDemo extends React.Component {
static propTypes = {
// 會把props的值傳給PropTypes.number函數,對其進行數字驗證。如果沒滿足要求則拋出一個錯誤。
max: PropTypes.number
};
constructor(props) {
super(props);
}
render() {
return(
<div>
<h2>值 - {this.props.max}</h2>
</div>
);
}
}
react-Novice03\app\src\App.js
import React from 'react';
import FormDemo from "./components/FormDemo";
import UnControl from "./components/UnControl";
import RefDemo from "./components/RefDemo";
import PropsDefaultValueDemo from "./components/PropsDefaultValueDemo";
import PropTypesDemo from "./components/PropTypesDemo";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
}
}
render() {
return (
<div className="App">
{/*<FormDemo />*/}
{/*<hr/>*/}
{/*<UnControl value={this.state.v1} />*/}
{/*<RefDemo />*/}
{/*<PropsDefaultValueDemo />*/}
{/*<PropsDefaultValueDemo max={1000} />*/}
<PropTypesDemo max={10} />
</div>
)
}
}
export default App;
<PropTypesDemo max={'csdn'} />
報錯:有一個不可接受的(失敗的)props
類型數據,string
類型的,但我們只允許number
。
必傳參,不能省略
max: PropTypes.any.isRequired
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.07-1
Branch:branch2
commit description:v1.07-1-example07-1(props驗證測試)
tag:v1.07-1
6. 2. 1. 2 example07-02
需求:max
的值必須在10-100之間
react-Novice03\app\src\components\PropTypesDemo.js
import React from 'react';
import PropTypes from 'prop-types';
export default class PropTypesDemo extends React.Component {
static propTypes = {
// props對象 propName:props名稱 componentName 組件名稱
max(props, propName, componentName) {
console.log('....');
let v = props[propName]; // 取值方式
console.log(v);
if (v < 10 || v > 100) {
throw new RangeError('max的值必須在10-100之間');
}
}
};
constructor(props) {
super(props);
}
render() {
return(
<div>
<h2>值 - {this.props.max}</h2>
</div>
);
}
}
react-Novice03\app\src\App.js
import React from 'react';
import FormDemo from "./components/FormDemo";
import UnControl from "./components/UnControl";
import RefDemo from "./components/RefDemo";
import PropsDefaultValueDemo from "./components/PropsDefaultValueDemo";
import PropTypesDemo from "./components/PropTypesDemo";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
}
}
render() {
return (
<div className="App">
{/*<FormDemo />*/}
{/*<hr/>*/}
{/*<UnControl value={this.state.v1} />*/}
{/*<RefDemo />*/}
{/*<PropsDefaultValueDemo />*/}
{/*<PropsDefaultValueDemo max={1000} />*/}
{/*<PropTypesDemo max={19} />*/}
<PropTypesDemo max={9} />
{/*<PropTypesDemo max={'csdn'} />*/}
{/*<PropTypesDemo />*/}
</div>
)
}
}
export default App;
<PropTypesDemo max={19} />
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.07-2
Branch:branch2
commit description:v1.07-2-example07-2(props驗證測試——需求:max的值必須在10-100之間)
tag:v1.07-2
7. children
一個組件通過 props 除了能給獲取自身屬性上的值,還可以獲取被組件包含的內容,也就是外部子組件,前面我們寫的組件更多的是作爲一個單標籤組件,實際應用中很多組件是雙標籤的,也就是可以包含內容的,也可稱爲:容器組件,那麼組件包含的內容,我們就可以通過 props.children 來獲取
7. 1 dialog 組件
7. 1. 1 css
.dialog {
position: fixed;
left: 50%;
top: 30%;
transform: translateX(-50%) translateY(-50%) ;
border-radius: 2px;
box-shadow: 0 1px 3px rgba(0,0,0,.3);
box-sizing: border-box;
background: #fff;
width: 60%;
}
.dialog_header {
padding: 20px 20px 0;
text-align: left;
}
.dialog_title {
font-size: 16px;
font-weight: 700;
color: #1f2d3d;
}
.dialog_content {
padding: 30px 20px;
color: #48576a;
font-size: 14px;
text-align: left;
}
.dialog_close_btn {
position: absolute;
right: 10px;
top: 5px;
}
.dialog_close_btn:before {
content: 'x';
color: #999;
font-size: 20px;
cursor: pointer;
}
7. 1. 2 dialog.js
import React from 'react';
import './dialog.css';
export default class Dialog extends React.Component {
static defaultProps = {
title: '這是默認標題'
}
render() {
return(
<div className="dialog">
<i className="dialog_close_btn"></i>
<div className="dialog_header">
<span className="dialog_title">{this.props.title}</span>
</div>
<div className="dialog_content">
{this.props.children}
</div>
</div>
);
}
}
7. 1. 3 example08
需求:模擬對話框
7. 1. 3. 1 example08-1
實現框子
對話框樣式,可以自己完善,或者參考小迪github
上的源碼。
react-Novice03\app\src\components\ChildrenDemo.js
import React from 'react';
import './dialog.css';
export default class ChildrenDemo extends React.Component {
static defaultProps = {
title: '這是默認標題',
content: '這是默認的內容'
}
render() {
console.log(this.props);
return(
<div className="dialog">
<i className="dialog_close_btn"></i>
<div className="dialog_header">
<span className="dialog_title">{this.props.title}</span>
</div>
<div className="dialog_content">{this.props.content}</div>
</div>
);
}
}
react-Novice03\app\src\App.js
import React from 'react';
import FormDemo from "./components/FormDemo";
import UnControl from "./components/UnControl";
import RefDemo from "./components/RefDemo";
import PropsDefaultValueDemo from "./components/PropsDefaultValueDemo";
import PropTypesDemo from "./components/PropTypesDemo";
import ChildrenDemo from "./components/ChildrenDemo";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
}
}
render() {
return (
<div className="App">
{/*<FormDemo />*/}
{/*<hr/>*/}
{/*<UnControl value={this.state.v1} />*/}
{/*<RefDemo />*/}
{/*<PropsDefaultValueDemo />*/}
{/*<PropsDefaultValueDemo max={1000} />*/}
{/*<PropTypesDemo max={19} />*/}
{/*<PropTypesDemo max={9} />*/}
{/*<PropTypesDemo max={'csdn'} />*/}
{/*<PropTypesDemo />*/}
<ChildrenDemo title={'CSDN'} content={"https://mp.csdn.net/"}/>
</div>
)
}
}
export default App;
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.08-1
Branch:branch2
commit description:v1.08-1-example08-1(需求:模擬對話框——實現框子)
tag:v1.08-1
7. 1. 3. 2 example08-2
如果對話框中內容放入一個表單
<ChildrenDemo title={'CSDN'} content={
<form>
<p>
用戶名:<input type="text"/>
</p>
</form>
}/>
如果我們還想往裏嵌套組件,參數寫起來就像之前的遞歸一樣,一層一層可讀性極差!
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.08-2
Branch:branch2commit description:v1.08-2-example08-2(需求:模擬對話框——嵌套表單)
tag:v1.08-2
7. 1. 3. 3 example08-3
實際上結構不要寫在屬性上,可以把當成容器一樣使用。這就更類似於我們平時寫的html了,可讀性會更好。
<ChildrenDemo title={'CSDN'}>
<form>
<p>
用戶名:<input type="text"/>
</p>
</form>
</ChildrenDemo>
但是在頁面中並不存在此結構,貌似並沒有將其放入props
。這其實就是我們常說的影子dom
和子元素
之間的差異性了。ChildrenDemo
的背後其實是我們在src/components/ChildrenDemo.js
中的render
中返回值,而在這裏包含的是我們所寫的表單標籤。
假設ChildrenDemo
是一個盒子,而src/components/ChildrenDemo.js
中的render
中返回值就是修飾盒子的邊框,這裏的我們所寫的表單標籤就是盒子裏存放的物品(子元素),兩者不是一套東西。
我們看到log
中,其實這些物品是放在props
的children
屬性中,這其實是一個虛擬dom
節點(其實就是把這些物品解析成虛擬dom
)。
再修改代碼:
react-Novice03\app\src\components\ChildrenDemo.js
import React from 'react';
import './dialog.css';
export default class ChildrenDemo extends React.Component {
static defaultProps = {
title: '這是默認標題',
content: '這是默認的內容'
}
render() {
console.log(this.props);
return(
<div className="dialog">
<i className="dialog_close_btn"></i>
<div className="dialog_header">
<span className="dialog_title">{this.props.title}</span>
</div>
<div className="dialog_content">{this.props.children ? this.props.children : this.props.content}</div>
</div>
);
}
}
react-Novice03\app\src\App.js
import React from 'react';
import FormDemo from "./components/FormDemo";
import UnControl from "./components/UnControl";
import RefDemo from "./components/RefDemo";
import PropsDefaultValueDemo from "./components/PropsDefaultValueDemo";
import PropTypesDemo from "./components/PropTypesDemo";
import ChildrenDemo from "./components/ChildrenDemo";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
}
}
render() {
return (
<div className="App">
{/*<FormDemo />*/}
{/*<hr/>*/}
{/*<UnControl value={this.state.v1} />*/}
{/*<RefDemo />*/}
{/*<PropsDefaultValueDemo />*/}
{/*<PropsDefaultValueDemo max={1000} />*/}
{/*<PropTypesDemo max={19} />*/}
{/*<PropTypesDemo max={9} />*/}
{/*<PropTypesDemo max={'csdn'} />*/}
{/*<PropTypesDemo />*/}
<ChildrenDemo title={'CSDN'}>
<form>
<p>
用戶名:<input type="text"/>
</p>
</form>
</ChildrenDemo>
</div>
)
}
}
export default App;
這其實是經常會用到的,假若我們寫一個組件,我們不可能把組件的所有內容都能夠定義好,很多時候,這個組件其實是一個容器型組件,它裏邊還可以放很多其他東西,這個時候可由外部決定。有兩種方式,第一種直接傳參,但有的時候結構可能會很複雜,傳參會很麻煩(可讀性極差),我們就可以用類似html的嵌套形式即可(解析爲虛擬dom
放在children
屬性裏)。
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.08-3
Branch:branch2commit description:v1.08-3-example08-3(需求:模擬對話框——最終版)
tag:v1.08-3
7. 1. 4 example09
7. 1. 4. 1 example09-1
實現一個可拖拽的div
元素組件,即擴展爲選擇哪個元素,就可以進行拖拽。如第三方庫不具備此特性,我們怎樣將其加工爲可拖拽呢(禁止更改其源碼)?
類似於面向對象的設計模式—裝飾者模式:通過一種無侵入式的方式(不需要修改此對象的本身,而對這個對象進行功能等擴展,得到一個具有新特性的對象),來擴展某個元素的特性。
react-Novice03\app\src\components\Drag.js
import React from 'react';
export default class Drag extends React.Component {
constructor(props) {
super(props);
}
render() {
// 被拖拽的元素
let el = this.props.children;
console.log(el);// 虛擬dom對象,不是原生js對象
return(
<div>
{this.props.children}
</div>
);
}
}
react-Novice03\app\src\App.js
import React from 'react';
import FormDemo from "./components/FormDemo";
import UnControl from "./components/UnControl";
import RefDemo from "./components/RefDemo";
import PropsDefaultValueDemo from "./components/PropsDefaultValueDemo";
import PropTypesDemo from "./components/PropTypesDemo";
import ChildrenDemo from "./components/ChildrenDemo";
import Drag from "./components/Drag";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
}
}
render() {
return (
<div className="App">
{/*<FormDemo />*/}
{/*<hr/>*/}
{/*<UnControl value={this.state.v1} />*/}
{/*<RefDemo />*/}
{/*<PropsDefaultValueDemo />*/}
{/*<PropsDefaultValueDemo max={1000} />*/}
{/*<PropTypesDemo max={19} />*/}
{/*<PropTypesDemo max={9} />*/}
{/*<PropTypesDemo max={'csdn'} />*/}
{/*<PropTypesDemo />*/}
{/*<ChildrenDemo title={'CSDN'}>*/}
{/* <form>*/}
{/* <p>*/}
{/* 用戶名:<input type="text"/>*/}
{/* </p>*/}
{/* </form>*/}
{/*</ChildrenDemo>*/}
{/*裝飾者*/}
<Drag>
<div ref={el => {
console.log(el); // 解析過後的元素
}} style={{
width: '100px',
height: '100px',
position: 'absolute',
background: 'red',
}}></div>
</Drag>
</div>
)
}
}
export default App;
虛擬dom
對象裏有一個ref屬性,這裏會解析成真實dom
,我們來完善拖拽!
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.09-1
Branch:branch2commit description:v1.09-1-example09-1(需求:React實現拖拽——框子)
tag:v1.09-1
7. 1. 4. 2 example09-2
關於拖拽原理小迪不再重複了,請參考小迪的詳細探究拖拽原理的博客。
推薦 Event事件學習實用路線(10)——Event事件之拖拽原理思路詳解
import React from 'react';
import FormDemo from "./components/FormDemo";
import UnControl from "./components/UnControl";
import RefDemo from "./components/RefDemo";
import PropsDefaultValueDemo from "./components/PropsDefaultValueDemo";
import PropTypesDemo from "./components/PropTypesDemo";
import ChildrenDemo from "./components/ChildrenDemo";
import Drag from "./components/Drag";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
v1: 1
}
this.moveElemnt = this.moveElemnt.bind(this);
}
moveElemnt(el) {
let startPos = {} // 1. 鼠標點擊的位置
let boxPos={} // 2. 元素的初始位置
el.addEventListener("mousedown", (e)=>{
// 保存
// 初始鼠標位置
startPos.x = e.clientX;
startPos.y = e.clientY;
// 元素的初始位置
boxPos.x = parseFloat(getComputedStyle(el).left);
boxPos.y = parseFloat(getComputedStyle(el).top);
document.addEventListener("mousemove", drag);
let i = 1;
el.addEventListener("mouseup", ()=>{
console.log(i++);
document.removeEventListener("mousemove", drag);
},{
// 只綁定一次事件
once:true
});
});
function drag(e){
let nowPos = {
x : e.clientX,
y : e.clientY
}
let dis = {
x : nowPos.x - startPos.x,
y : nowPos.y - startPos.y
}
let newBoxPos = {
left : boxPos.x + dis.x,
top : boxPos.y + dis.y
}
// 限制左側
if (newBoxPos.left < 0){
newBoxPos.left = 0;
}
// 限制右側
let maxLeft = document.documentElement.clientWidth - el.offsetWidth;
if (newBoxPos.left > maxLeft){
newBoxPos.left = maxLeft;
}
// 限制上側
if (newBoxPos.top < 0){
newBoxPos.top = 0;
}
// 限制下側
let maxTop = document.documentElement.clientHeight;
if (newBoxPos.top > maxTop) {
newBoxPos.top = maxTop;
}
el.style.top = newBoxPos.top + 'px';
el.style.left = newBoxPos.left + 'px';
}
}
render() {
return (
<div className="App">
{/*<FormDemo />*/}
{/*<hr/>*/}
{/*<UnControl value={this.state.v1} />*/}
{/*<RefDemo />*/}
{/*<PropsDefaultValueDemo />*/}
{/*<PropsDefaultValueDemo max={1000} />*/}
{/*<PropTypesDemo max={19} />*/}
{/*<PropTypesDemo max={9} />*/}
{/*<PropTypesDemo max={'csdn'} />*/}
{/*<PropTypesDemo />*/}
{/*<ChildrenDemo title={'CSDN'}>*/}
{/* <form>*/}
{/* <p>*/}
{/* 用戶名:<input type="text"/>*/}
{/* </p>*/}
{/* </form>*/}
{/*</ChildrenDemo>*/}
{/*裝飾者*/}
<Drag>
<div ref={el => {
console.log(el); // 解析過後的元素
this.moveElemnt(el);
}} style={{
width: '100px',
height: '100px',
position: 'absolute',
background: 'red',
}}></div>
</Drag>
</div>
)
}
}
export default App;
參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v1.09-2
Branch:branch2
commit description:v1.09-2-example09-2(需求:React實現拖拽——最終版)
tag:v1.09-2
(後續待補充)