webpack --help -p :
shortcut for --optimize-minimize --define process.env.NODE_ENV="production"
理解JSX語法
JSX語法注意點(diǎn):
- 是 XML 不是 HTML,所以所有的標(biāo)簽都要閉合,如 <img /> 或 <div />。
- 每段 JSX 里的 XML 只能有一個(gè)根元素,不然就報(bào)錯(cuò)。
- XML 里面可以用
{}混入 任何 JS 代碼
JSX語法的理解:分為tagname,attributes,children三項(xiàng)來理解
例如: const jsx = <div>Hello Jsx</div>
經(jīng)過babel之后為:
var jsx = React.createElement(
"div",
null,
"Hello Jsx"
);
這樣可以理解為:
{
tagname: "div",
attributes: null,
children: "Hello Jsx"
}
組件的tagname就不是標(biāo)準(zhǔn)的html標(biāo)簽了,如果有props,那么attributes也不是null了,對(duì)于children來說,如果內(nèi)部還有嵌套標(biāo)簽,那么依次做一個(gè)遞歸。
組件
react聲明組件時(shí),第一個(gè)字母必須大寫。
兩種寫法:
- class component
class Welcome extends React.Component {
render () {
return <h1>hello,{this.props.name}</h1>;
}
}
或者:
2.functional component
function Welcome(props) {
return <h1>hello,{props.name}</h1>
}
組件中的數(shù)據(jù)源
props (props是父組件到子組件的,props should be pure,即不予許直接修改props)
state (state是自身維護(hù)的數(shù)據(jù)狀態(tài),但也只可以通過setState修改)
組件中不可以改變props的值,state是組件中可以改變的東西
但是要使用this.setState()方法才能改變state的值
關(guān)于setState() , 參考
要理解2點(diǎn):
- setState不會(huì)立刻改變React組件中state的值
- 函數(shù)式的setState用法(即setState()方法可以接受一個(gè)函數(shù)作為參數(shù))
import React from 'react';
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {
date: new Date()
}
setInterval(() => {
this.setState({
date: new Date()
})
})
}
render () {
return (
<div>
<h1>hello, {this.props.name}</h1>
<h2>{this.state.date.toString()}</h2>
</div>
)
}
}
export default Welcome;
生命周期
React 的生命周期包括三個(gè)階段:mount(掛載)、update(更新)和 unmount(移除)
mount
mount 就是第一次讓組件出現(xiàn)在頁面中的過程。這個(gè)過程的關(guān)鍵就是 render 方法。React 會(huì)將 render 的返回值(一般是虛擬 DOM,也可以是 DOM 或者 null)插入到頁面中。
這個(gè)過程會(huì)暴露幾個(gè)鉤子(hook)方便你往里面加代碼:
- constructor() 初始化props 和 state
- componentWillMount() 準(zhǔn)備插入render中return的內(nèi)容
- render() 開始插入
- componentDidMount() 插入之后想進(jìn)行的操作
update
mount 之后,如果數(shù)據(jù)有任何變動(dòng),就會(huì)來到 update 過程,這個(gè)過程有 5 個(gè)鉤子:
- 1.componentWillReceiveProps(nextProps) - 我要讀取 props 啦!
- shouldComponentUpdate(nextProps, nextState) - 請(qǐng)問要不要更新組件?true / false
- componentWillUpdate() - 我要更新組件啦!
- render() - 更新!
- componentDidUpdate() - 更新完畢啦!
unmount
當(dāng)一個(gè)組件將要從頁面中移除時(shí),會(huì)進(jìn)入 unmount 過程,這個(gè)過程就一個(gè)鉤子:
- componentWillUnmount() - 我要死啦!
你可以在這個(gè)組件死之前做一些清理工作。
一般在下列鉤子中應(yīng)用setState():
- componentWillMount
- componentDidMount
- componentWillReceiveProps
事件綁定
- 首先明確一個(gè)概念,就是在
<div onClick=fn></div>這個(gè)DOM綁定中,onClick后面的fn是一個(gè)函數(shù),而不是一個(gè)函數(shù)執(zhí)行的結(jié)果,所以不能寫成<div onClick=fn()></div>。(重點(diǎn)?。?/li>
明確了函數(shù)綁定時(shí),寫的是一個(gè)函數(shù)而不是函數(shù)運(yùn)行的結(jié)果之后,繼續(xù)明確第二個(gè)概念:
- bind(),對(duì)于一個(gè)函數(shù)
fn來說,fn.bind(window)同樣是一個(gè)函數(shù),只不過運(yùn)行的Context指定為了window。fn.bind(window)本身并沒有執(zhí)行(所以仍然是一個(gè)函數(shù)而不是函數(shù)運(yùn)行的結(jié)果?。?/li>
明確了上述2點(diǎn)之后,再來看react中的事件綁定。
先來看一個(gè)App組件:
import React from 'react'
import ReactDom from 'react-dom'
import './index.css'
const rootDom = document.querySelector('#root')
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: true
}
}
render () {
return (
//這里點(diǎn)擊的時(shí)候的this已經(jīng)不是當(dāng)前組件了,所以要bind為當(dāng)前組件,不然是沒有testClick函數(shù)的
<div onClick={this.testClick.bind(this)}>
<button>{this.state.isOn ? 'ON' : 'OFF' }</button>
</div>
)
}
testClick () {
this.setState( (prevState) => { //setState()也能傳函數(shù)作為參數(shù),沒什么好說的
return {
isOn: !prevState.isOn
}
})
}
}
ReactDom.render(<App></App>,rootDom)
這么一看就能理解react中的事件綁定了。
- 還有一個(gè)小問題,對(duì)于標(biāo)簽?zāi)J(rèn)行為的阻止,不像Vue一樣有
.prevent這種修飾符,還是要自己寫的。
來看個(gè)例子:
import React from 'react'
import ReactDom from 'react-dom'
import './index.css'
const rootDom = document.querySelector('#root')
let App = () => {
function preventClick (e) { // 注意這里的e,是react自己傳過來的 本身沒做任何操作
e.preventDefault() //
console.log('default click has been prevent')
}
return (
<div>
{/*上面已經(jīng)說過,綁定事件只能寫函數(shù),不能寫函數(shù)的運(yùn)行結(jié)果,所以如果寫了preventClick()就會(huì)報(bào)錯(cuò)*/}
<a onClick={preventClick}>click me</a>
</div>
)
}
ReactDom.render(<App></App>,rootDom)
對(duì)于上述問題 又引申出了一個(gè)小問題,如果我想在阻止a標(biāo)簽的同時(shí),事件處理函數(shù)還想接收一些其他的參數(shù)進(jìn)行處理怎么寫呢。
對(duì)于React,上面例子的e.preventDefault()中的e是React自己傳過來的。我們?nèi)绻认雽慹,還想有自己規(guī)定的一些參數(shù)的話,要這么寫:
let App = () => {
function preventClick (str,e) { // 如果有其他自定義的參數(shù),那么e永遠(yuǎn)是最后一項(xiàng),而且同樣也是React自己傳過來的。
e.preventDefault() //
console.log(str)
}
return (
<div>
{/*上面已經(jīng)說過,綁定事件只能寫函數(shù),不能寫函數(shù)的運(yùn)行結(jié)果,所以如果寫了preventClick()就會(huì)報(bào)錯(cuò)*/}
<a onClick={preventClick.bind(this,'testString')}>click me</a>
{/*這里的preventClick.bind(this,'testString')同樣也是一個(gè)函數(shù),并不是函數(shù)運(yùn)行結(jié)果*/}
</div>
)
}
上面的例子中,我們想在preventClick()中傳一些自己用的參數(shù),那么在點(diǎn)擊的時(shí)候,就要bind到組件本身上,然后再傳入想要的參數(shù),注意e是不用我們自己寫到參數(shù)中去的,回到函數(shù)本身的參數(shù)上,自定義的參數(shù)在前,e永遠(yuǎn)是最后一項(xiàng)。
擺脫React中操蛋的bind(this)的方法
上面的事件綁定中已經(jīng)提到了bind,因?yàn)?strong>事件觸發(fā)時(shí)的this已經(jīng)不是當(dāng)前組件本身了,所以我們要將處理函數(shù)的this重新設(shè)置為當(dāng)前組件,所以有了隨便一個(gè)函數(shù)后面都要bind(this)的情況。
先來看個(gè)例子:
class App extends React.Component {
constructor(props) {
super(props)
}
render (){
return (
<div>
<button onClick={this.testClick.bind(this)}>click me</button>
</div>
)
}
testClick() { // 普通函數(shù)在被調(diào)用的時(shí)候,就有了this,所以要加bind()到App組件上
console.log(this)
}
}
上述例子中,testClick是App組件上的函數(shù),點(diǎn)擊時(shí)的this已經(jīng)不是App組件了,所以不能正確調(diào)用。要將其寫為this.testClick.bind(this)而不是this.testClick。
為了擺脫上述這種煩人的寫法,我們可以有如下幾種方法:
- 在
constructor中將當(dāng)前函數(shù)重新賦值。
class App extends React.Component {
constructor(props) {
super(props)
this.testClick = this.testClick.bind(this) //這的this是App組件,直接將testClick重新賦值一下
}
render (){
return (
<div>
<button onClick={this.testClick}>click me</button>
</div>
)
}
testClick() { // 普通函數(shù)在被調(diào)用的時(shí)候,就有了this,所以要加bind()到App組件上
console.log(this)
}
}
我們?cè)赾onstructor中給testClick重新賦了一次值,testClick變?yōu)榱艘粋€(gè)運(yùn)行上下文為App組件的函數(shù),下面直接onClick={this.testClick}即可。
- public class fields syntax(不知道怎么翻譯,就是利用箭頭函數(shù)沒有this的特性)
class App extends React.Component {
constructor(props) {
super(props)
}
render (){
return (
<div>
<button onClick={this.testClick}>click me</button>
</div>
)
}
testClick = () => { //箭頭函數(shù)沒有本身的this 所以定義的時(shí)候this已經(jīng)確定為App組件了,所以不用bind
console.log(this)
}
}
我們利用箭頭函數(shù)本身沒有this的特性,在App中定義testClick的時(shí)候,testClick的this就已經(jīng)確定為當(dāng)前上下文App組件了。所以后面直接調(diào)用onClick={this.testClick}即可。
- 在回調(diào)中使用箭頭函數(shù)
class App extends React.Component {
constructor(props) {
super(props)
}
render (){
return (
<div>
<button onClick={(e) => {return this.testClick(e)}}>click me</button>
</div>
)
}
testClick() {
console.log(this)
}
}
每次渲染組件時(shí),都會(huì)生成一個(gè)全新的回調(diào)。但在有時(shí)候?qū)⑵渥鳛閜rop傳給子組件時(shí),會(huì)引發(fā)一次額外的渲染。所以,建議用constructor 和public class fields syntax兩種方式避免bind(this)這種寫法。
值得一提的是:
上面說的想給事件處理函數(shù)傳遞另外自定義的參數(shù)時(shí),綁定事件的時(shí)候是不用寫e的,因?yàn)閞eact是自動(dòng)幫你把e作為最后一個(gè)參數(shù)傳遞的。
<button onClick={this.testClick.bind(this, id)}>click me</button>
就像上述例子一樣,我們想傳一個(gè)額外的參數(shù)id,在id后面是不需要寫e的,this.testClick.bind(this, id,e)這種是不用寫的。
但是! 在我們提到的第3種方法,回調(diào)中使用函數(shù)的情況下,是需要開發(fā)者自己寫上e的!
<button onClick={(e) => this.testClick(id, e)}>click me</button>
就像這樣,自定義參數(shù)id的后面還要寫上e。
詳情參考文檔:With an arrow function, we have to pass it explicitly, but with bind any further arguments are automatically forwarded.
React中的ref
想直接修改原生DOM或者是組件的時(shí)候,可以使用ref。
- 原生DOM上
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
{/*ref callback*/}
<input type="text" ref={(input) => this.testRef = input} />
<input type="button" onClick={this.focus.bind(this)} value="Click" />
</div>
)
}
focus() {
this.testRef.focus();
}
}
上述例子就是一個(gè)原生DOM上使用ref的例子,react在原生的input加載完成后,通過一個(gè)回調(diào)函數(shù),(input) => this.testRef = input,這里的回調(diào)參數(shù)input就是底層的DOM,接受DOM作為參數(shù),存到testRef中。引用的時(shí)候,直接就引用到了原生的DOM。
- ref ====> class Component
// ref ===> class Component
class App extends React.Component {
render() {
return (
<TestComponent ref={(testComponent) => this.classCom = testComponent}></TestComponent>
)
}
componentDidMount () {
console.log(this.classCom.state)
}
}
class TestComponent extends React.Component {
constructor() {
super()
this.state = {
name: 'children Component'
}
}
render() {
return (
<div>
<input type="text"/>
</div>
)
}
}
這里的ref回調(diào)函數(shù)中,(testComponent) => this.classCom = testComponent的參數(shù)為已經(jīng)加載的 React 實(shí)例,我們可以在父組件中通過this.classCom訪問到它。
- ref ====> functional Component
你不能在函數(shù)式組件上使用 ref 屬性,因?yàn)樗鼈儧]有實(shí)例,但是可以對(duì)其內(nèi)部的原生DOM使用ref,參見第一條。
React中突變數(shù)據(jù)的處理
經(jīng)常會(huì)遇到設(shè)置了state之后,進(jìn)行setState()了之后,頁面沒有進(jìn)行渲染的情況。(尤其是使用PureComponent的時(shí)候)。
這種情況經(jīng)常是由于操作習(xí)慣不好造成了原數(shù)據(jù)結(jié)構(gòu)的突變。
- 先來解釋什么是原數(shù)據(jù)突變
let arr = [1]
arr.push(2)
console.log(arr) //[1, 2]
我們有一個(gè)數(shù)組,進(jìn)行了push操作之后,原先的數(shù)組的值改變了,由[1]變?yōu)榱?code>[1,2]。
看下一個(gè)例子:
let arr2 = [1]
let arr3 = arr2.concat([2])
console.log(arr2) //[1]
console.log(arr3) //[1, 2]
同樣是一個(gè)數(shù)組,進(jìn)行了concat操作之后,原先的arr2的值并沒有發(fā)生改變,我們把concat之后的結(jié)果賦給了一個(gè)新變量arr3來儲(chǔ)存。
- React中setState()時(shí)我們?cè)撛趺磳憯?shù)據(jù)
上面我們提到了push操作會(huì)改變?cè)瓟?shù)據(jù),這在React中是不應(yīng)該的。
setState時(shí),我們應(yīng)該return的是一個(gè)新對(duì)象,而不是原對(duì)象。所以我們要避免那些會(huì)修改原數(shù)據(jù)的操作,例如push。
比如說,我們?cè)谝粋€(gè)組件中通過push()修改了數(shù)組,然后有一個(gè)PureComponent子組件把這個(gè)數(shù)組作為props渲染頁面,這時(shí)候雖然我們?cè)诟附M件中修改了數(shù)組的值,但是setState()return的時(shí)候,原數(shù)據(jù)結(jié)構(gòu)已經(jīng)變了,這時(shí)候雖然本身的state已經(jīng)改變了,但是傳到子組件的props并沒有改變。
為了避免這種情況發(fā)生,我們一般可以采用concat等不會(huì)引起原數(shù)據(jù)突變的操作。
如果非要用push的話,可以通過Object.assgin()或者...spread語法來進(jìn)行一份數(shù)據(jù)的拷貝,在進(jìn)行操作。
比如說上述的arr,我們可以這么操作:
this.state = {
arr: [1]
};
//進(jìn)行setState()
this.setState( (prevState) => {
return {
arr: [...prevState.arr,2]
}
})
如果目標(biāo)數(shù)據(jù)是對(duì)象的話,可以通過Object.assgin()來進(jìn)行操作