目錄
主要內容:函數式組件,類式組件,組件複用(數據抽取/傳入),組件狀態state,組件交互(綁定事件,獲取事件對象,原生DOM對象,獲取原生DOM對象),狀態更新setstate,props與state區別,組件通信與數據流,key的唯一性。
1.組件類型
web
組件就是對 web
中的數據、結構、方法等進行封裝,複用,與 JavaScript
中功能函數封裝類似,但更關注的是對 web
元素(標籤)的封裝與擴展(擴展:webComponent
)
React
提供了兩種組件構建方式
- 函數式組件
- 類式組件
1.1函數式組件
在 React.js 中,定義一個組件的最簡單的方式就是 函數
function Kaikeba() {
return (
<div>
<h2>開課吧!</h2>
</div>
);
}
ReactDOM.render(
<Kaikeba />,
document.getElementById('app')
);
- 函數的名稱就是組件的名稱
- 函數的返回值就是組件要渲染的內容
**函數的名稱(組件的名稱),必須是首字母大寫
1.2類式組件
我們還可以通過 類(class)類定義組件
class Miaov extends React.Component {
render() {
return (
<div>
<h2>妙味!</h2>
</div>
);
}
}
- 組件類必須繼承 React.Component
- 組件類必須有 render 方法
- render 方法的返回值就是組件要渲染的內容
**類的名稱(組件的名稱),也必須是首字母大寫
2.創建FriendList組件——案例
不從實際案例開始的知識點就是耍 LM
樣式 :
.friend-list {
border: 1px solid #000000;
width: 200px;
}
.friend-group dt {
padding: 10px;
background-color: rgb(64, 158, 255);
font-weight: bold;
}
.friend-group dd {
padding: 10px;
display: none;
}
.friend-group.expanded dd {
display: block;
}
.friend-group dd.checked {
background: green;
}
創建 FriendList 組件:
import React from 'react';
import './FriendList.css'
function FriendList(){
return (
<div className="friend-list">
<div className="friend-group">
<dt>家人</dt>
<dd>爸爸</dd>
<dd>媽媽</dd>
</div>
<div className="friend-group">
<dt>朋友</dt>
<dd>張三</dd>
<dd>李四</dd>
<dd>王五</dd>
</div>
<div className="friend-group">
<dt>客戶</dt>
<dd>阿里</dd>
<dd>騰訊</dd>
<dd>頭條</dd>
</div>
</div>
);
}
export default FriendList;
3.組件複用——數據抽取
爲了提高組件的複用性,通常會把組件中的一些可變數據提取出來
let datas = {
family: {
title: '家人',
list: [
{name: '爸爸'},
{name: '媽媽'}
]
},
friend: {
title: '朋友',
list: [
{name: '張三'},
{name: '李四'},
{name: '王五'}
]
},
customer: {
title: '客戶',
list: [
{name: '阿里'},
{name: '騰訊'},
{name: '頭條'}
]
}
};
export default datas;
import React from 'react';
import './FriendList.css';
import datas from './datas/friends1';
function FriendList(){
return (
<div className="friend-list">
<div className="friend-group">
{Object.keys(datas).map((key, index) => (
<div className="friend-group" key={key}>
<dt>{datas[key].title}</dt>
{datas[key].list.map(list => <dd key={list.name}>{list.name}</dd>)}
</div>
))}
</div>
</div>
);
}
export default FriendList;
4.組件複用——數據傳入
數據雖然分離了,但是爲了降低組件與數據之前的依賴,我們應該儘量避免在組件內部直接訪問數據,通過傳參的方法來進行解耦。
4.1property
使用組件的時候通過 標籤屬性-property 的方式傳入數據,在組件內部通過構造函數參數(如果是函數組件,則通過函數參數)來接收傳入的數據 。
<組件名稱 屬性名稱="值" />
// 使用表達式
<組件名稱 屬性名稱={表達式} />
ReactDOM.render(
<FriendList datas={datas} />,
document.getElementById('app')
);
4.2接收參數——props
在組件對應的函數或類中通過:
- 函數式組件:通過函數的第一個參數來接收
- 類式組件:通過類的構造函數第一個參數來接收
無論是函數式組件還是類式組件,都會把傳入的參數組裝成一個對象:
<組件名稱 屬性名稱1="值1" 屬性名稱二={表達式二} />
// 函數式組件
function 組件名稱(參數) {
// 參數的結構爲
參數 = {
屬性名稱1: "值1",
屬性名稱二: 表達式二的值
}
return <div>組件結構</div>
}
// 類式組件
class 組件名稱 extends React.Component {
constructor(參數) {
super(參數);
this.props = {
屬性名稱1: "值1",
屬性名稱2: 表達式二的值
}
}
render() {
return <div>組件結構</div>
}
}
在類式組件中,需要注意:
當子類重寫 constructor ,則必須調用父類 super - 類的知識點
把接收到的參數傳入父類構造函數,父類構造函數中會創建一個對象屬性:props 類存儲傳入的參數數據
4.3通過參數動態渲染組件結構
函數式組件:
APP.js
import React from 'react';
// import FriendList from './compenents/FriendList';
import FriendListProps from './compenents/FriendListProps';
import datas from './compenents/datas/friends1';
function App() {
return (
<div className="App">
<h1>hi, react!</h1>
{/* <FriendList /> */}
<FriendListProps datas={datas} name='列表渲染'/>
</div>
);
}
export default App;
FriendsListProp.js:
import React from 'react';
import './FriendList.css';
// import datas from './datas/friends1';
function FriendListProps(props){
// 函數式組件:通過函數的第一個參數來接收
console.log(props);
let {datas} = props;
return (
<div className="friend-list">
<div className="friend-group">
{Object.keys(datas).map((key, index) => (
<div className="friend-group" key={key}>
<dt>{datas[key].title}</dt>
{datas[key].list.map(list => <dd key={list.name}>{list.name}</dd>)}
</div>
))}
</div>
</div>
);
}
export default FriendListProps;
類式組件:
friends.js:
let datas = {
jser: {
title: 'JS愛好者',
list: [
{name: '姍鳳'},
{name: '姐大'},
{name: '鼕鼕'}
]
},
tser: {
title: 'TS愛好者',
list: [
{name: 'lili'},
{name: '君君'},
{name: '香兒'}
]
}
};
export default datas;
APP.js
import React from 'react';
// import FriendList from './compenents/FriendList';
// import FriendListProps from './compenents/FriendListProps';
import FriendList2 from './compenents/FriendList2';
// import datas from './compenents/datas/friends1';
import datas from './compenents/datas/friends2';
function App() {
return (
<div className="App">
<h1>hi, react!</h1>
{/* <FriendList /> */}
{/* <FriendListProps datas={datas} name='列表渲染'/> */}
<FriendList2 datas={datas} name='列表渲染'/>
</div>
);
}
export default App;
FriendList2.js:
import React from 'react';
import './FriendList.css';
class FriendList2 extends React.Component {
// 類式組件:通過類的構造函數第一個參數來接收
constructor(props){
super(props);
this.props = props;
}
render(){
let {datas} = this.props;
return (
<div className="friend-list">
<div className="friend-group">
{Object.keys(datas).map((key, index) => (
<div className="friend-group" key={key}>
<dt>{datas[key].title}</dt>
{datas[key].list.map(list => <dd key={list.name}>{list.name}</dd>)}
</div>
))}
</div>
</div>
);
}
}
export default FriendList2;
4.4子組件提取
組件與類一樣,是一個不斷提取的過程,當我們發現某個部分可複用或者結構複雜的時候,我們可以對它再次進行提取 。
class FriendGroup extends React.Component {
constructor(props) {
super(props);
}
render() {
let {data} = this.props;
return (
<div className="friend-group">
<dt>{data.title}</dt>
{data.list.map((list, index) => {
<dd key={index}>{list}</dd>
})}
</div>
);
}
}
class FriendList extends React.Component {
constructor(props) {
super(props);
}
render() {
let {datas} = this.props;
return (
<div className="friend-list">
{Object.keys(datas).map((key, index) => (
<FriendGroup data={datas[key]} key={key} />
))}
</div>
);
}
}
5.組件狀態state
有的時候,一個組件除了可以接收外部傳入的數據,還會有屬於自己的內部私有數據,同時組件私有數據的更新還會導致組件的重新渲染,比如上面的例子中的每一個好友面板會有一個內部私有數據來控制面板的展開與收縮,我們又把這種數據稱爲組件狀態 。
state:存儲組件狀態的對象
class FriendGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: false
};
}
render() {
let {data} = this.props;
let {expanded} = this.state;
return (
<div className={[
"friend-group",
expanded && "expanded"
].join(' ')}>
<dt>{data.title}</dt>
{data.list.map((list, index) => {
<dd key={index}>{list}</dd>
})}
</div>
);
}
}
6.組件交互
一個組件除了有結構、樣式,有的時候還會有交互,如上面的例子:現在我們希望當用戶點擊組件面板 title 的時候,面板組件的展開收縮狀態就會發生改變 。
6.1綁定事件
class FriendGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: false
};
this.expand = this.click.expand();
}
expand() {
this.setState({
expanded: !this.state.expanded
});
}
render() {
let {data} = this.props;
let {expanded} = this.state;
return (
<div className={[
"friend-group",
expanded && "expanded"
].join(' ')}>
<dt onClick={this.expand}>{data.title}</dt>
{data.list.map((list, index) => {
<dd key={index}>{list}</dd>
})}
</div>
);
}
}
React.js 的事件綁定需要注意:
事件名稱是駝峯命名的
事件綁定函數的 this 指向
通過 bind 改變 this 指向,爲了能夠在方法中調用組件對象的屬性和其它方法,我們需要把 this 指向組件
通過箭頭函數處理 this
6.2獲取事件對象
事件綁定函數的第一個參數是事件對象
6.3原生DOM對象
通過事件對象可以獲取到原生 DOM 對象
6.4獲取原生DOM對象
有的時候我們也是需要操作原生 DOM 對象的,除了可以通過事件源來獲取,還可以通過 ref 的方式來獲取
class MyComponent extends React.Component {
render() {
return (
<div>
<input ref={el=>this.el=el} />
<button onClick={this.todo.bind(this)}>Button</button>
</div>
);
}
todo() {
console.log(this.el.value);
}
}
7.狀態更新
當我們希望更新組件狀態的時候,不要直接修改 state 的值,而是需要調用 setState 方法來進行更新
7.1setState方法
setState 方法由父類 React.Component 提供,當該方法被調用的時候,組件的 state 會被更新,同時會重新調用組件的 render 方法對組件進行渲染
不要直接修改 state,而是調用 setState 方法來更新,組件構造函數是唯一可以對 state 直接賦值(初始化)的位置
this.setState({key:val})
7.1.1更新異步
出於性能考慮,setState 方法的修改並不是立即生效的
// this.state.val = 0
this.setState({
val: this.state.val + 1
});
// this.state.val 的值並不是 1
console.log(this.state.val);
7.1.2更新合併
React 會把多個 setState 合併成一個調用
// this.state.val = 0
this.setState({
val: this.state.val + 1
});
this.setState({ // 因爲異步的問題,this.state.val 的值在這裏還是0
val: this.state.val + 1
});
Object.assign(
previousState,
{val: state.val + 1},
{val: state.val + 1},
...
)
7.1.3回調函數
如果,有多個 setState 的調用,後一次的修改如果需要依賴上一次修改的值,那麼可以使用函數的方式
// this.state.val = 0
this.setState((state, props) => {
return {val: state.val + 1}
});
this.setState((state, props) => {
return {val: state.val + 1}
});
8.props和state的區別
state 的主要作用是用於組件保存、控制、修改自己的可變狀態,在組件內部進行初始化,也可以在組件內部進行修改,但是組件外部不能修改組件的 state
props 的主要作用是讓使用該組件的父組件可以傳入參數來配置該組件,它是外部傳進來的配置參數,組件內部無法控制也無法修改
state 和 props 都可以決定組件的外觀和顯示狀態。通常,props 做爲不變數據或者初始化數據傳遞給組件,可變狀態使用 state
使用 props 就不要使用 state
8.1無狀態式組件
沒有 state 的組件,我們稱爲無狀態組件,因爲狀態會帶來複雜性,所以,通常我們推薦使用無狀態組件,也鼓勵編寫無狀態組建 。
8.2函數式組件
函數式組件沒有 state ,所以通常我們編寫使用函數式組件來編寫無狀態組件
9.組件通信與數據流
在上面的案例中,我們實現了一個多選式的好友列表(每個獨立的面板都有自己獨立的狀態,互不影響)。下面我們再來看另外一種情況:單選式的好友列表(多組列表同時只能有一個爲展開狀態,也就是多個面板之間的狀態是互相影響的)
9.1狀態提升
通過分析可以發現,面板的展開狀態不在是組件內部私有狀態了,多個組件都會受到這個狀態的影響,也就是它們共享了一個狀態。爲了能夠讓多個不同組件共享同一個狀態,這個時候,我們把這個狀態進行提升,交給這些組件最近的公共父組件進行管理 。
class FriendGroup extends React.Component {
constructor(props) {
super(props);
this.changeExpand = this.changeExpand.bind(this);
}
changeExpand() {
typeof this.props.onExpand === 'function' && this.props.onExpand(this.props.index);
}
render() {
let {data, expandedIndex, index} = this.props;
return (
<div onClick={this.changeExpand} className={[
"friend-group",
expandedIndex === index && "expanded"
].join(' ')}>
<dt>{data.title}</dt>
{data.list.map((list, index) => {
<dd key={index}>{list}</dd>
})}
</div>
);
}
}
class FriendList extends React.Component {
constructor(props) {
super(props);
this.state = {
expandedIndex: 0
}
this.changeExpandedIndex = this.changeExpandedIndex.bind(this);
}
changeExpandedIndex(expandedIndex) {
this.setState({
expandedIndex
});
}
render() {
let {datas} = this.props;
return (
<div onExpand={this.changeExpandedIndex} className="friend-list">
{Object.keys(datas).map((key, index) => (
<FriendGroup data={datas[key]} index={index} expandedIndex={expandedIndex} key={key} />
))}
</div>
);
}
}
9.2數據流
在 React.js 中,數據是從上自下流動(傳遞)的,也就是一個父組件可以把它的 state / props 通過 props 傳遞給它的子組件,但是子組件不能修改 props - React.js 是單向數據流,如果子組件需要修改父組件狀態(數據),是通過回調函數方式來完成的。
9.2.1props函數
父組件在調用子組件的時候,傳入一個函數類型的 props
<FriendGroup data={datas[key]} index={index} expandedIndex={expandedIndex} key={key} />
9.2.2更新父組件
在父組件的代碼中,通過 setState 重新渲染父組件,父組件的更新會重新渲染子組件 。
10.key
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
arr: ['html', 'css', 'javascript']
}
this.sort = this.sort.bind(this);
}
sort() {
this.setState({
arr: this.state.arr.sort( _ => Math.random() - .5 )
});
}
render() {
let {arr} = this.state;
return(
<div>
<ul>
{arr.map( (v, k) => (
<li>
<input type="checkbox"/>
<span>{v}</span>
</li>
) )}
</ul>
<button onClick={this.sort}>亂序</button>
</div>
);
}
}
ReactDOM.render(
<MyComponent />,
document.getElementById('app')
);
通過控制檯查看結構變化
10.1渲染優化
- 用JS對象模擬DOM樹 : Virtual DOM
- 通過 DOM 操作 把 Virtual DOM 生成真實的 DOM
- 後期的更新,會比較 新老 Virtual DOM ,通過 diff 算法優化更新操作
10.1.1Virtual DOM
原生 DOM 對象結構太複雜,同時存在許多情況下用不到的一些特性
Virtual DOM 本質上與 原生 DOM 沒有區別,都是 JavaScript 對象,只是它的結構更加簡潔
10.1.2Diffing 算法
React.js 不只是模擬了一個 DOM,同時在針對 DOM 進行渲染的時候