1 使用了react官方腳手架:create-react-app
https://github.com/facebook/create-react-app
npm run eject 可以打開配置文件
自定義配置文件
執行安裝: npx create-react-app ts-with-react --typescript
npx 只有在npm5.2以上版本才有
1、避免安裝全局模塊:臨時命令,使用后刪除,再次執行的時候再次下載
2、調用項目內部安裝的模塊用起來更方便:比如 在package.json文件中安裝了一個依賴:mocha,如果想執行有兩種方法:
2.1 在scripts中定義
{ "script":{ "test":"mocha --version" } }
2.2 在命令行中執行 node_modules/.bin/mocha --version
而使用npx的話 則只需要執行: npx mocha --version
首先新建一個hello組件:components/hello.jsx
import React from 'react' interface IHelloProps { message?: string;//因為設置了props的默認值,這里用?表示有無都可 } /* const Hello = (props:IHelloProps) => { return <h2>{props.message}</h2> } */ const Hello: React.FC<IHelloProps> = (props) => { //FC是 react 官方提供的一個 interface 的簡寫:FunctionComponent,接收一個范型 //這樣Hello包含了很多靜態方法 return <h2>{props.message}</h2> } Hello.defaultProps = { message: "Hello World" } export default Hello
## 什么是react hook
React 16.8 帶來的全新特性,使用函數式組件,即將替代class組件的寫法;
解決的問題
1.組件很難復用狀態邏輯,一般使用HOC或者render Props
2.復雜組件難以理解,尤其是生命周期函數

例如,獲取props中的id,需要在 componentDidMount和 componentDidUpdate 中同時定義;監聽函數也需要在 componentDidMounted和componentWillUnmount中監聽和注銷
3.react組件一直是函數,使用Hook完全擁抱函數
## State Hook
新建一個likeBotton.tsx 文件【點贊按鈕】
import React, { useState } from 'react'
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0);
const [on,setOn] = useState(true);
return (
<>
<button onClick={() => {setLike(like + 1)}}>
{like} 👍
</button>
<button onClick={() => {setOn(!on)}}>
{on?'ON':'OFF'}
</button>
</>
)
}
export default LikeButton
網絡請求、DOM操作 和函數渲染頁面關系不大的 放在副作用中
Effect Hook
1.無需清除的Effect
2.需要清除的Effect
如果是以前的話,需要在兩個生命周期中執行同樣的代碼
componentDidMount(){ document.title = `you clicked ${this.state.count} times`; } componentDidUpdate(){ document.title = `you clicked ${this.state.count} times`; }
使用useEffect:
import React, { useState,useEffect } from 'react'
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0);
const [on,setOn] = useState(true);
useEffect(()=>{//useEffect會在組件每次渲染的時候,都會被調用
document.title = `點擊了${like}次`
})
return (
<>
<button onClick={() => {setLike(like + 1)}}>
{like} 👍
</button>
<button onClick={() => {setOn(!on)}}>
{on?'ON':'OFF'}
</button>
</>
)
}
export default LikeButton
2.需要清除的Effect
功能: 使用useEffect 完成一個鼠標跟蹤器
原來在class中的實現方法:
componentDidMount(){ document.addEventListener('click',this.updateMouse); } componentWillUnMount(){ document.addEventListener('click',this.updateMouse); }
使用useEffect,就要忘記以前的生命周期,頁面掛載之后執行useEffect
import React, { useState, useEffect } from 'react'
const MouseTracker: React.FC = () => {
const [ positions, setPositions ] = useState({x: 0, y: 0})
useEffect(() => {
console.log('add effect', positions.x)//執行順序2:add effect 0;//執行順序6:add effect 102
const updateMouse= (e: MouseEvent) => {
console.log('inner') //執行順序3
setPositions({ x: e.clientX, y: e.clientY })
}
document.addEventListener('click', updateMouse)
return () => { //如果不加return函數中卸載功能,每次渲染函數組件的時候,都會執行useEffect
//導致注冊很多個 監聽事件,
console.log('remove effect', positions.x) //執行順序5:remove effect 0
document.removeEventListener('click', updateMouse)
}
}, [])
//執行順序1:before render 0//點擊頁面后,執行順序4:before render 102
console.log('before render', positions.x)
return (
<p>X: {positions.x}, Y : {positions.y}</p>
)
}
export default MouseTracker
但是每次更新函數組件,都要執行useEffect ,下面介紹如何控制useEffect函數的執行
useEffect(() => { console.log('document title effect is running') document.title = `點擊了${like}次`; document.addEventListener('click', updateMouse) return ()=>{ document.removeEventListener('click', updateMouse) } }, []) //useEffect第二個參數,如果是空數組,表示只在掛載元素后執行1次,但是其返回函數,會在組件卸載的時候執行 //如果不寫第二個參數,則不做限制,組件中的任何一個data變化的時候,都會執行useEffect函數 //如果第二個參數寫上變量后,則只有該變量發生變化的時候,才執行 useEffect
## 自定義Hook——新建的hooks,必須以 use 開頭
1 將組件邏輯提取到可重用的函數中
例如兩個組件中均用到了公共的邏輯,獲取跟隨鼠標移動的坐標:
新建hooks/useMousePosition.jsx
import React, { useState, useEffect } from 'react'
const useMousePosition = () => {
const [ positions, setPositions ] = useState({x: 0, y: 0})
useEffect(() => {
console.log('add effect', positions.x)
const updateMouse= (e: MouseEvent) => {
setPositions({ x: e.clientX, y: e.clientY })
}
document.addEventListener('mousemove', updateMouse)
return () => {
console.log('remove effect', positions.x)
document.removeEventListener('mousemove', updateMouse)
}
}, [])
return positions
}
export default useMousePosition
在其他組件中引入方式:
import useMousePosition from './hooks/useMousePosition' const App:React.FC = () => { const positions = useMousePosition(); return ( <div> <p>X:{position.x},Y:{positions.y}</p> </div> ) }
## 編輯一個hooks,功能是:加載圖片功能,替換之前的HOC方式,簡單方便,相當於定義一個函數,然后再外面用到的地方調用,可以傳參,可以控制調用的時機:
interface IShowResult{ message:string, status:string } //定義一個組件 const DogShow:React.FC<{data:IShowResult}> = ({data})=>{ return ( <> <h2>show:{data.status}</h2> <img src={data.message}/> </> ) } const App:React.FC=()=>{ //高階組件,將一個組件用參數形式傳入,然后經過包裹后返回一個新的組件,達到公用包裹組件的功能 const WrappedDogShow = withLoader(DogShow,'https://dog.ceo/api/breeds/image/random'); return ( <WrappedDogShow/> ) }
高階組件
// high order component import React from 'react' import axios from 'axios' interface ILoaderState { data: any, isLoading: boolean } interface ILoaderProps { data: any, } const withLoader = <P extends ILoaderState>(WrappedComponent: React.ComponentType<P>, url: string) => { return class LoaderComponent extends React.Component<Partial<ILoaderProps>, ILoaderState> { constructor(props: any) { super(props) this.state = { data: null, isLoading: false } } componentDidMount() { this.setState({ isLoading: true, }) axios.get(url).then(result => { this.setState({ data: result.data, isLoading: false }) }) } render() { const { data, isLoading } = this.state return ( <> { (isLoading || !data) ? <p>data is loading</p> : <WrappedComponent {...this.props as P} data={data} /> } </> ) } } } export default withLoader
//父組件 <template> <Loading><DogImg/></Lading> </template> //子組件 <template> <div> <p v-if="load">loading...</p> <div v-else><slot><img src="/location.png"/></slot></div> </div> </template> <script> { //需要傳給父組件的data,使用 v-model 或者 vuex均可 } </script>
PS:react父子組件使用vue中的slot
import React, { Component } from 'react';
import Children from './Children';
class Father extends Component {
render() {
return (
<div>
<Children>
<em>1111111111111111</em>
</Children>
<p>我是父組件</p>
</div>
)
}
}
export default Father;
子組件
import React, { Component } from 'react';
class Children extends Component{
constructor(props){
super(props)
}
render(){
return (
<div>
{this.props.children //這里調用了其內部}
<h1>我是子組件</h1>
</div>
)
}
}
export default Children;
import { useState, useEffect } from 'react'
import axios from 'axios'
const useURLLoader = (url: string, deps: any[] = []) => {
//第二個入參:deps,是決定該函數什么時候更新,因為放在了 useEffect 的第二個參數中
//默認是空數組,也就是只執行一次
const [data, setData] = useState<any>(null)
const [loading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
axios.get(url).then(result => {
setData(result.data)
setLoading(false)
})
}, deps)
return [data, loading]
}
export default useURLLoader
import useURLLoader from './hooks/useURLLoader' interface IShowResult { message: string; status: string; } const App: React.FC = () => { const [show,setShow] = useState(true) const [data, loading] = useURLLoader('http://www.xxx.com',[show]);//每次show變化的時候,都要更新hooks中的useEffect函數 const dogResult = data as IShowResult; return ( <div className="App"> { loading?<p>圖片下載中。。。</p> :<img src={dogResult && dogResult.message}/> } <p> <button onClick={() => {setShow(!show)}}>Refresh dog photo</button> </p> </div> ); } export default App;
useRef的使用
先來看 state和props的每次改變,其實都相當於閉包,每次的數據都是獨立的:
比如下面代碼,點擊了第一個button后,三秒后才彈出 like 的值;在此期間多次點擊第二個按鈕,當前的 like 已經是其他的數字,但是異步執行的時候,保留的仍然是當時的數值;
import React, { useState, useEffect } from 'react'
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0)
useEffect(() => {
console.log('document title effect is running')
document.title = `點擊了${like}次`
}, [like])
function handleAlertClick() {
setTimeout(() => {
alert('you clicked on ' + like)
}, 3000)
}
return (
<>
<button onClick={() => {setLike(like + 1)}}>
{like} 👍
</button>
<button onClick={handleAlertClick}> Alert!
</button>
</>
)
}
export default LikeButton
所以使用useRef,注意的是修改ref的值並不會觸發 render函數的更新,之所以函數更新是因為修改了 useState的值
import React, { useState, useEffect, useRef } from 'react'
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0);//state在每一個render中都是獨立的值,相當於閉包的存在
const likeRef = useRef(0);//useRef 是一個函數,初始值是0;ref在所有的render中都保持的唯一的引用
useEffect(() => {
console.log('document title effect is running')
document.title = `點擊了${like}次`
}, [like])
function handleAlertClick() {
setTimeout(() => {
alert('you clicked on ' + likeRef.current)//改動了這里,以current獲取值
}, 3000)
}
return (
<>
<button onClick={() => {setLike(like + 1;likeRef.current++)}}>
{like} 👍
</button>
<button onClick={handleAlertClick}> Alert!
</button>
</>
)
}
export default LikeButton
import React, { useState, useEffect, useRef } from 'react'
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0);
const didMountRef = useRef(false);
useEffect(()=>{ //只有在數據更新的時候,才去執行
if(didMountRef.current){
console.log('is update');
}else {
didMountRef.current = true;
}
})
return (
<>
<button onClick={() => {setLike(like + 1)}}>
{like} 👍
</button>
<button onClick={handleAlertClick}> Alert!
</button>
</>
)
}
export default LikeButton
useRef常用來訪問節點:
import React, { useState, useEffect, useRef } from 'react'
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0);
const domRef = useRef<HTMLInputElement>(null);//雖然初始化的時候是null,但是其實是DOM元素的范性
useEffect(()=>{ //每次渲染render的時候后會去執行,一旦獲取到dom元素,則獲取光標,而用 useRef 定義的DOM元素,是唯一的
if(domRef && domRef.current){
domRef.current.focus()
}
})
return (
<>
<input type="text" ref = {domRef}/>
<button onClick={() => {setLike(like + 1)}}>
{like} 👍
</button>
<button onClick={handleAlertClick}> Alert!
</button>
</>
)
}
export default LikeButton
使用useContext解決數據多層透傳的問題
首先在最外層,父組件中使用:
1 import React, { useState } from 'react'; 2 import LikeButton from './components/LikeButton' 3 import Hello from './components/Hello' 4 import useURLLoader from './hooks/useURLLoader' 5 6 interface IThemeProps { 7 [key: string]: {color: string; background: string;} 8 } 9 const themes: IThemeProps = { 10 'light': { 11 color: '#000', 12 background: '#eee', 13 }, 14 'dark': { 15 color: '#fff', 16 background: '#222', 17 } 18 } 19 export const ThemeContext = React.createContext(themes.light) //定義context,且使用export導出 20 const App: React.FC = () => { 21 const [ show, setShow ] = useState(true) 22 //使用 ThemeContext.Provider 包裹所有的組件 23 return ( 24 <div className="App"> 25 <ThemeContext.Provider value={themes.dark}> 26 <header className="App-header"> 27 <img src={logo} className="App-logo" alt="logo" /> 28 <LikeButton /> 29 <Hello /> 30 </header> 31 </ThemeContext.Provider> 32 </div> 33 ); 34 } 35 36 export default App;
import React, { useState, useEffect, useRef, useContext } from 'react'//導入useContext
import { ThemeContext } from '../App' //自組件使用,首先要調用context
const LikeButton: React.FC = () => {
const theme = useContext(ThemeContext)
const style = {
background: theme.background,
color: theme.color,
}
return (
<>
<input type="text" ref={domRef} />
<button style={style}>
{like} 👍
</button>
</>
)
}
export default LikeButton
