古希腊掌管小面包的神 發表於 2020-4-5 11:58:00

使用react搭建组件库(二):react+typescript

<p>1 使用了react官方脚手架:create-react-app</p>
<p>https://github.com/facebook/create-react-app</p>
<h2><code>npm run eject 可以打开配置文件</code></h2>
<p>自定义配置文件</p>
<p>执行安装:&nbsp;<span class="cnblogs_code">npx create-react-app ts-<span style="color: rgba(0, 0, 255, 1)">with</span>-react --typescript</span>&nbsp;</p>
<p>npx 只有在npm5.2以上版本才有</p>
<p>1、避免安装全局模块:临时命令,使用后删除,再次执行的时候再次下载</p>
<p>2、调用项目内部安装的模块用起来更方便:比如 在package.json文件中安装了一个依赖:mocha,如果想执行有两种方法:</p>
<p>&nbsp;2.1 在scripts中定义&nbsp;  </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
   </span>"script"<span style="color: rgba(0, 0, 0, 1)">:{
       </span>"test":"mocha --version"<span style="color: rgba(0, 0, 0, 1)">
    }
}</span></pre>
</div>
<p>2.2 在命令行中执行&nbsp;&nbsp;<span class="cnblogs_code">node_modules/.bin/mocha --version</span>&nbsp;</p>
<p>而使用npx的话 则只需要执行:&nbsp;&nbsp;<span class="cnblogs_code">npx mocha --version</span>&nbsp;</p>
<p>首先新建一个hello组件:components/hello.jsx</p>
<div class="cnblogs_code">
<pre>import React from 'react'<span style="color: rgba(0, 0, 0, 1)">
interface IHelloProps {
message</span>?: string;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">因为设置了props的默认值,这里用?表示有无都可</span>
<span style="color: rgba(0, 0, 0, 1)">}

</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
const Hello = (props:IHelloProps) =&gt; {
return &lt;h2&gt;{props.message}&lt;/h2&gt;
}
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
const Hello: React.FC</span>&lt;IHelloProps&gt; = (props) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">FC是 react 官方提供的一个 interface 的简写:FunctionComponent,接收一个范型</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这样Hello包含了很多静态方法</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> &lt;h2&gt;{props.message}&lt;/h2&gt;
<span style="color: rgba(0, 0, 0, 1)">}
Hello.defaultProps </span>=<span style="color: rgba(0, 0, 0, 1)"> {
message: </span>"Hello World"<span style="color: rgba(0, 0, 0, 1)">
}

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> Hello</pre>
</div>
<p>&nbsp;</p>
<p>## 什么是react hook</p>
<p>React 16.8 带来的全新特性,使用函数式组件,即将替代class组件的写法;</p>
<p>解决的问题</p>
<p>1.组件很难复用状态逻辑,一般使用HOC或者render Props</p>
<p>2.复杂组件难以理解,尤其是生命周期函数</p>
<p><img src="https://img2020.cnblogs.com/blog/693756/202004/693756-20200405141414210-2063235129.png"></p>
<p>例如,获取props中的id,需要在 componentDidMount和 componentDidUpdate 中同时定义;监听函数也需要在 componentDidMounted和componentWillUnmount中监听和注销</p>
<p>3.react组件一直是函数,使用Hook完全拥抱函数</p>
<p>&nbsp;</p>
<p>## State Hook</p>
<p>&nbsp;新建一个likeBotton.tsx 文件【点赞按钮】</p>
<div class="cnblogs_code">
<pre>import React, { useState } from 'react'<span style="color: rgba(0, 0, 0, 1)">
const LikeButton: React.FC </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const </span>= useState(0<span style="color: rgba(0, 0, 0, 1)">);
const </span>= useState(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;&gt;
    &lt;button onClick={() =&gt; {setLike(like + 1)}}&gt;<span style="color: rgba(0, 0, 0, 1)">
      {like} 👍
    </span>&lt;/button&gt;
    &lt;button onClick={() =&gt; {setOn(!on)}}&gt;<span style="color: rgba(0, 0, 0, 1)">
      {on</span>?'ON':'OFF'<span style="color: rgba(0, 0, 0, 1)">}
    </span>&lt;/button&gt;
    &lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">)
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> LikeButton</pre>
</div>
<p>网络请求、DOM操作 和函数渲染页面关系不大的 放在副作用中</p>
<p>Effect Hook</p>
<p>1.无需清除的Effect</p>
<p>2.需要清除的Effect</p>
<p>如果是以前的话,需要在两个生命周期中执行同样的代码</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">componentDidMount(){
document.title </span>= `you clicked ${<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.count} times`;
}
componentDidUpdate(){
document.title </span>= `you clicked ${<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state.count} times`;
}</span></pre>
</div>
<p>使用useEffect:</p>
<div class="cnblogs_code">
<pre>import React, { useState,useEffect } from 'react'<span style="color: rgba(0, 0, 0, 1)">
const LikeButton: React.FC </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const </span>= useState(0<span style="color: rgba(0, 0, 0, 1)">);
const </span>= useState(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
useEffect(()</span>=&gt;{<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">useEffect会在组件每次渲染的时候,都会被调用</span>
    document.title =<span style="color: rgba(0, 0, 0, 1)"> `点击了${like}次`
})
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;&gt;
    &lt;button onClick={() =&gt; {setLike(like + 1)}}&gt;<span style="color: rgba(0, 0, 0, 1)">
      {like} 👍
    </span>&lt;/button&gt;
    &lt;button onClick={() =&gt; {setOn(!on)}}&gt;<span style="color: rgba(0, 0, 0, 1)">
      {on</span>?'ON':'OFF'<span style="color: rgba(0, 0, 0, 1)">}
    </span>&lt;/button&gt;
    &lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">)
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> LikeButton</pre>
</div>
<p>2.需要清除的Effect</p>
<p>功能: 使用useEffect 完成一个鼠标跟踪器</p>
<p>原来在class中的实现方法:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">componentDidMount(){
document.addEventListener(</span>'click',<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.updateMouse);
}
componentWillUnMount(){
document.addEventListener(</span>'click',<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.updateMouse);
}</span></pre>
</div>
<p>使用useEffect,就要忘记以前的生命周期,页面挂载之后执行useEffect</p>
<div class="cnblogs_code">
<pre>import React, { useState, useEffect } from 'react'<span style="color: rgba(0, 0, 0, 1)">
const MouseTracker: React.FC </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const [ positions, setPositions ] </span>= useState({x: 0, y: 0<span style="color: rgba(0, 0, 0, 1)">})
useEffect(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    console.log(</span>'add effect', positions.x)<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">执行顺序2:add effect 0;//执行顺序6:add effect 102</span>
    const updateMouse= (e: MouseEvent) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      console.log(</span>'inner') <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">执行顺序3</span>
<span style="color: rgba(0, 0, 0, 1)">      setPositions({ x: e.clientX, y: e.clientY })
    }
    document.addEventListener(</span>'click'<span style="color: rgba(0, 0, 0, 1)">, updateMouse)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> () =&gt; { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果不加return函数中卸载功能,每次渲染函数组件的时候,都会执行useEffect</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">导致注册很多个 监听事件,</span>
      console.log('remove effect', positions.x) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">执行顺序5:remove effect 0</span>
      document.removeEventListener('click'<span style="color: rgba(0, 0, 0, 1)">, updateMouse)
    }
}, [])
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">执行顺序1:before render 0//点击页面后,执行顺序4:before render 102</span>
console.log('before render'<span style="color: rgba(0, 0, 0, 1)">, positions.x)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;p&gt;X: {positions.x}, Y : {positions.y}&lt;/p&gt;
<span style="color: rgba(0, 0, 0, 1)">)
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> MouseTracker</pre>
</div>
<p>&nbsp;</p>
<p>但是每次更新函数组件,都要执行useEffect ,下面介绍如何控制useEffect函数的执行</p>
<div class="cnblogs_code">
<pre>useEffect(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
console.log(</span>'document title effect is running'<span style="color: rgba(0, 0, 0, 1)">)
document.title </span>=<span style="color: rgba(0, 0, 0, 1)"> `点击了${like}次`;
document.addEventListener(</span>'click'<span style="color: rgba(0, 0, 0, 1)">, updateMouse)
</span><span style="color: rgba(0, 0, 255, 1)">return</span> ()=&gt;<span style="color: rgba(0, 0, 0, 1)">{
    document.removeEventListener(</span>'click'<span style="color: rgba(0, 0, 0, 1)">, updateMouse)
}
}, [])
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">useEffect第二个参数,如果是空数组,表示只在挂载元素后执行1次,但是其返回函数,会在组件卸载的时候执行</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)">如果不写第二个参数,则不做限制,组件中的任何一个data变化的时候,都会执行useEffect函数</span><span style="color: rgba(0, 128, 0, 1)">
//</span><span style="color: rgba(0, 128, 0, 1)">如果第二个参数写上变量后,则只有该变量发生变化的时候,才执行 useEffect</span></pre>
</div>
<p>## 自定义Hook——新建的hooks,<span style="color: rgba(255, 0, 0, 1)"><strong>必须以 use 开头</strong></span></p>
<p>1 将组件逻辑提取到可重用的函数中</p>
<p>&nbsp;例如两个组件中均用到了公共的逻辑,获取跟随鼠标移动的坐标:</p>
<p>新建hooks/useMousePosition.jsx</p>
<div class="cnblogs_code">
<pre>import React, { useState, useEffect } from 'react'<span style="color: rgba(0, 0, 0, 1)">

const useMousePosition </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const [ positions, setPositions ] </span>= useState({x: 0, y: 0<span style="color: rgba(0, 0, 0, 1)">})
useEffect(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    console.log(</span>'add effect'<span style="color: rgba(0, 0, 0, 1)">, positions.x)
    const updateMouse</span>= (e: MouseEvent) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      setPositions({ x: e.clientX, y: e.clientY })
    }
    document.addEventListener(</span>'mousemove'<span style="color: rgba(0, 0, 0, 1)">, updateMouse)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      console.log(</span>'remove effect'<span style="color: rgba(0, 0, 0, 1)">, positions.x)
      document.removeEventListener(</span>'mousemove'<span style="color: rgba(0, 0, 0, 1)">, updateMouse)
    }
}, [])
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> positions
}

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> useMousePosition</pre>
</div>
<p>在其他组件中引入方式:</p>
<div class="cnblogs_code">
<pre>import useMousePosition from './hooks/useMousePosition'<span style="color: rgba(0, 0, 0, 1)">
const App:React.FC </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const positions</span>=<span style="color: rgba(0, 0, 0, 1)"> useMousePosition();
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;div&gt;
      &lt;p&gt;X:{position.x},Y:{positions.y}&lt;/p&gt;
    &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">)
}</span></pre>
</div>
<p>&nbsp;</p>
<p>## 编辑一个hooks,功能是:加载图片功能,替换之前的HOC方式,简单方便,相当于定义一个函数,然后再外面用到的地方调用,可以传参,可以控制调用的时机:<br><br></p>
<div>
<div>react 中的 HOC 高阶组件,就是一个函数,接受一个组件作为参数,返回一个新的组件;</div>
<div>react可以通过高阶组件来扩展,而vue需要通过mixins来扩展。</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">interface IShowResult{
    message:string,
    status:string
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义一个组件</span>
const DogShow:React.FC&lt;{data:IShowResult}&gt; = ({data})=&gt;<span style="color: rgba(0, 0, 0, 1)">{
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;&gt;
            &lt;h2&gt;show:{data.status}&lt;/h2&gt;
            &lt;img src={data.message}/&gt;
      &lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}

const App:React.FC</span>=()=&gt;<span style="color: rgba(0, 0, 0, 1)">{
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">高阶组件,将一个组件用参数形式传入,然后经过包裹后返回一个新的组件,达到公用包裹组件的功能</span>
    const WrappedDogShow = withLoader(DogShow,'https://dog.ceo/api/breeds/image/random'<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;WrappedDogShow/&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}</span></pre>
</div>
<p>高阶组件</p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> high order component</span>
import React from 'react'<span style="color: rgba(0, 0, 0, 1)">
import axios from </span>'axios'<span style="color: rgba(0, 0, 0, 1)">

interface ILoaderState {
data: any,
isLoading: </span><span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">
}
interface ILoaderProps {
data: any,
}
const withLoader </span>= &lt;P extends ILoaderState&gt;(WrappedComponent: React.ComponentType&lt;P&gt;, url: string) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> class LoaderComponent extends React.Component&lt;Partial&lt;ILoaderProps&gt;, ILoaderState&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    constructor(props: any) {
      super(props)
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> {
      data: </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,
      isLoading: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
      }
    }
    componentDidMount() {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.setState({
      isLoading: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
      })
      axios.get(url).then(result </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.setState({
          data: result.data,
          isLoading: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
      })
      })
    }
    render() {
      const { data, isLoading } </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;&gt;<span style="color: rgba(0, 0, 0, 1)">
          { (isLoading </span>|| !data) ? &lt;p&gt;data is loading&lt;/p&gt; :
            &lt;WrappedComponent {...<span style="color: rgba(0, 0, 255, 1)">this</span>.props as P} data={data} /&gt;
<span style="color: rgba(0, 0, 0, 1)">          }
      </span>&lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">      )
    }
}
}

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> withLoader</pre>
</div>
<div>
<div><span style="background-color: rgba(51, 153, 102, 1); color: rgba(255, 255, 255, 1)">功能是传入一个组件,在高阶组件中,有三个功能</span></div>
<div><span style="background-color: rgba(51, 153, 102, 1); color: rgba(255, 255, 255, 1)">1、发送传入的url请求,得到需要的data值;</span></div>
<div><span style="background-color: rgba(51, 153, 102, 1); color: rgba(255, 255, 255, 1)">2、有一定的判断逻辑,没有拿到数据的时候显示 &lt;p&gt;,否则显示 传入的组件</span></div>
<div><span style="background-color: rgba(51, 153, 102, 1); color: rgba(255, 255, 255, 1)">3、给传入的组件添加data值</span></div>
<div>&nbsp;</div>
<div>Vue中如何实现类似的功能呢?</div>
<div>&nbsp;</div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">父组件</span>
&lt;template&gt;
    &lt;Loading&gt;&lt;DogImg/&gt;&lt;/Lading&gt;
&lt;/template&gt;

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">子组件</span>
&lt;template&gt;
    &lt;div&gt;
      &lt;p v-<span style="color: rgba(0, 0, 255, 1)">if</span>="load"&gt;loading...&lt;/p&gt;
      &lt;div v-<span style="color: rgba(0, 0, 255, 1)">else</span>&gt;&lt;slot&gt;&lt;img src="/location.png"/&gt;&lt;/slot&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;<span style="color: rgba(0, 0, 0, 1)">
    {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">需要传给父组件的data,使用 v-model 或者 vuex均可</span>
<span style="color: rgba(0, 0, 0, 1)">    }
</span>&lt;/script&gt;</pre>
</div>
<p>PS:react父子组件使用vue中的slot</p>
<div class="cnblogs_code">
<pre>import React, { Component } from 'react'<span style="color: rgba(0, 0, 0, 1)">;
import Children from </span>'./Children'<span style="color: rgba(0, 0, 0, 1)">;

class Father extends Component {

render() {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
          &lt;Children&gt;
            &lt;em&gt;1111111111111111&lt;/em&gt;
          &lt;/Children&gt;
          &lt;p&gt;我是父组件&lt;/p&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">      
      )
}
}

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> Father;</pre>
</div>
<p>子组件</p>
<div class="cnblogs_code">
<pre>import React, { Component } from 'react'<span style="color: rgba(0, 0, 0, 1)">;
class Children extends Component{
    constructor(props){
      super(props)
    }
    render(){
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
            </span>&lt;div&gt;<span style="color: rgba(0, 0, 0, 1)">
                {</span><span style="color: rgba(0, 0, 255, 1)">this</span>.props.children <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这里调用了其内部} </span>
                &lt;h1&gt;我是子组件&lt;/h1&gt;
            &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">      )
    }
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> Children;</pre>
</div>
</div>
</div>
</div>
</div>
<div>首先定义 hooks/useURLLoader.tsx</div>
<div>
<div class="cnblogs_code">
<pre>import { useState, useEffect } from 'react'<span style="color: rgba(0, 0, 0, 1)">
import axios from </span>'axios'<span style="color: rgba(0, 0, 0, 1)">
const useURLLoader </span>= (url: string, deps: any[] = []) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">第二个入参:deps,是决定该函数什么时候更新,因为放在了 useEffect 的第二个参数中</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">默认是空数组,也就是只执行一次</span>
const = useState&lt;any&gt;(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
const </span>= useState(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
useEffect(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    setLoading(</span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
    axios.get(url).then(result </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      setData(result.data)
      setLoading(</span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
    })
}, deps)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> useURLLoader</pre>
</div>
<div>然后在 App.jsx 中调用</div>
<div>&nbsp;</div>
<div>
<div class="cnblogs_code">
<pre>import useURLLoader from './hooks/useURLLoader'<span style="color: rgba(0, 0, 0, 1)">
interface IShowResult {
message: string;
status: string;
}
const App: React.FC </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const </span>= useState(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
const </span>= useURLLoader('http://www.xxx.com',);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">每次show变化的时候,都要更新hooks中的useEffect函数</span>
const dogResult =<span style="color: rgba(0, 0, 0, 1)"> data as IShowResult;
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;div className="App"&gt;<span style="color: rgba(0, 0, 0, 1)">
    {
      loading</span>?&lt;p&gt;图片下载中。。。&lt;/p&gt;
      :&lt;img src={dogResult &amp;&amp; dogResult.message}/&gt;
<span style="color: rgba(0, 0, 0, 1)">    }
    </span>&lt;p&gt;
      &lt;button onClick={() =&gt; {setShow(!show)}}&gt;Refresh dog photo&lt;/button&gt;
    &lt;/p&gt;
    &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">);
}

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> App;</pre>
</div>
<p>&nbsp;</p>
</div>
</div>
<h4>&nbsp;useRef的使用</h4>
<div>
<p>先来看 state和props的每次改变,其实都相当于闭包,每次的数据都是独立的:</p>
<p>比如下面代码,点击了第一个button后,三秒后才弹出 like 的值;在此期间多次点击第二个按钮,当前的 like 已经是其他的数字,但是异步执行的时候,保留的仍然是当时的数值;</p>
<div class="cnblogs_code">
<pre>import React, { useState, useEffect } from 'react'<span style="color: rgba(0, 0, 0, 1)">
const LikeButton: React.FC </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const </span>= useState(0<span style="color: rgba(0, 0, 0, 1)">)
useEffect(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    console.log(</span>'document title effect is running'<span style="color: rgba(0, 0, 0, 1)">)
    document.title </span>=<span style="color: rgba(0, 0, 0, 1)"> `点击了${like}次`
}, )

</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> handleAlertClick() {
    setTimeout(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      alert(</span>'you clicked on ' + <span style="color: rgba(0, 0, 0, 1)">like)
    }, </span>3000<span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;&gt;
    &lt;button onClick={() =&gt; {setLike(like + 1)}}&gt;<span style="color: rgba(0, 0, 0, 1)">
      {like} 👍
    </span>&lt;/button&gt;
    &lt;button onClick={handleAlertClick}&gt; Alert!
    &lt;/button&gt;
    &lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">)
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> LikeButton</pre>
</div>
<p>所以使用useRef,注意的是修改ref的值并不会触发 render函数的更新,之所以函数更新是因为修改了 useState的值</p>
<div class="cnblogs_code">
<pre>import React, { useState, useEffect, useRef } from 'react'<span style="color: rgba(0, 0, 0, 1)">
const LikeButton: React.FC </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const </span>= useState(0);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">state在每一个render中都是独立的值,相当于闭包的存在</span>
const likeRef = useRef(0);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">useRef 是一个函数,初始值是0;ref在所有的render中都保持的唯一的引用</span>
useEffect(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    console.log(</span>'document title effect is running'<span style="color: rgba(0, 0, 0, 1)">)
    document.title </span>=<span style="color: rgba(0, 0, 0, 1)"> `点击了${like}次`
}, )

</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> handleAlertClick() {
    setTimeout(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      alert(</span>'you clicked on ' + likeRef.current)<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">改动了这里,以current获取值</span>
    }, 3000<span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;&gt;
    &lt;button onClick={() =&gt; {setLike(like + 1;likeRef.current++)}}&gt;<span style="color: rgba(0, 0, 0, 1)">
      {like} 👍
    </span>&lt;/button&gt;
    &lt;button onClick={handleAlertClick}&gt; Alert!
    &lt;/button&gt;
    &lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">)
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> LikeButton</pre>
</div>
<p>&nbsp;</p>
<div>
<div>前面useState只执行1次【模拟在生命周期:componentDidMount中执行】,是在第二个参数中,设置为[];</div>
<div>而如果useRef要求只执行1次【模拟在生命周期:componentDidMount中执行】,则设置标识位为false,改变后变为true,进行锁住;</div>
<div>这里实现的是,第一次不执行,待数据更新的时候再去执行</div>
<div>&nbsp;</div>
<div>
<div class="cnblogs_code">
<pre>import React, { useState, useEffect, useRef } from 'react'<span style="color: rgba(0, 0, 0, 1)">
const LikeButton: React.FC </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const </span>= useState(0<span style="color: rgba(0, 0, 0, 1)">);
const didMountRef </span>= useRef(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
useEffect(()</span>=&gt;{ <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">只有在数据更新的时候,才去执行</span>
    <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(didMountRef.current){
      console.log(</span>'is update'<span style="color: rgba(0, 0, 0, 1)">);
    }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      didMountRef.current </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
    }   
})
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;&gt;
    &lt;button onClick={() =&gt; {setLike(like + 1)}}&gt;<span style="color: rgba(0, 0, 0, 1)">
      {like} 👍
    </span>&lt;/button&gt;
    &lt;button onClick={handleAlertClick}&gt; Alert!
    &lt;/button&gt;
    &lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">)
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> LikeButton</pre>
</div>
<p>useRef常用来访问节点:</p>
<div class="cnblogs_code">
<pre>import React, { useState, useEffect, useRef } from 'react'<span style="color: rgba(0, 0, 0, 1)">
const LikeButton: React.FC </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const </span>= useState(0<span style="color: rgba(0, 0, 0, 1)">);
const domRef </span>= useRef&lt;HTMLInputElement&gt;(<span style="color: rgba(0, 0, 255, 1)">null</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">虽然初始化的时候是null,但是其实是DOM元素的范性</span>
useEffect(()=&gt;{ <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">每次渲染render的时候后会去执行,一旦获取到dom元素,则获取光标,而用 useRef 定义的DOM元素,是唯一的</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span>(domRef &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> domRef.current){
          domRef.current.focus()
      }
})
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;&gt;
    &lt;input type="text" ref = {domRef}/&gt;
    &lt;button onClick={() =&gt; {setLike(like + 1)}}&gt;<span style="color: rgba(0, 0, 0, 1)">
      {like} 👍
    </span>&lt;/button&gt;
    &lt;button onClick={handleAlertClick}&gt; Alert!
    &lt;/button&gt;
    &lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">)
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> LikeButton</pre>
</div>
<p>&nbsp;</p>
<p><strong>使用useContext解决数据多层透传的问题</strong></p>
<p>首先在最外层,父组件中使用:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> import React, { useState } from 'react'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> import LikeButton from './components/LikeButton'
<span style="color: rgba(0, 128, 128, 1)"> 3</span> import Hello from './components/Hello'
<span style="color: rgba(0, 128, 128, 1)"> 4</span> import useURLLoader from './hooks/useURLLoader'
<span style="color: rgba(0, 128, 128, 1)"> 5</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">interface IThemeProps {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">: {color: string; background: string;}
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> const themes: IThemeProps =<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">10</span>'light'<span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(0, 128, 128, 1)">11</span>    color: '#000'<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">12</span>    background: '#eee'<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)"> },
</span><span style="color: rgba(0, 128, 128, 1)">14</span>'dark'<span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(0, 128, 128, 1)">15</span>   color: '#fff'<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">16</span>   background: '#222'<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">19</span> export const ThemeContext = React.createContext(themes.light) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义context,且使用export导出</span>
<span style="color: rgba(0, 128, 128, 1)">20</span> const App: React.FC = () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">21</span>   const [ show, setShow ] = useState(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">22</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用 ThemeContext.Provider 包裹所有的组件</span>
<span style="color: rgba(0, 128, 128, 1)">23</span>   <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 128, 128, 1)">24</span>   &lt;div className="App"&gt;
<span style="color: rgba(0, 128, 128, 1)">25</span>       &lt;ThemeContext.Provider value={themes.dark}&gt;
<span style="color: rgba(0, 128, 128, 1)">26</span>       &lt;header className="App-header"&gt;
<span style="color: rgba(0, 128, 128, 1)">27</span>         &lt;img src={logo} className="App-logo" alt="logo" /&gt;
<span style="color: rgba(0, 128, 128, 1)">28</span>         &lt;LikeButton /&gt;
<span style="color: rgba(0, 128, 128, 1)">29</span>         &lt;Hello /&gt;
<span style="color: rgba(0, 128, 128, 1)">30</span>       &lt;/header&gt;
<span style="color: rgba(0, 128, 128, 1)">31</span>       &lt;/ThemeContext.Provider&gt;
<span style="color: rgba(0, 128, 128, 1)">32</span>   &lt;/div&gt;
<span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">35</span>
<span style="color: rgba(0, 128, 128, 1)">36</span> export <span style="color: rgba(0, 0, 255, 1)">default</span> App;</pre>
</div>
<p>&nbsp;</p>
</div>
</div>
&nbsp;然后在子组件中调用:</div>
<div>
<div class="cnblogs_code">
<pre>import React, { useState, useEffect, useRef, useContext } from 'react'<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">导入useContext</span>
import { ThemeContext } from '../App' <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">自组件使用,首先要调用context</span>
const LikeButton: React.FC = () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    const theme </span>=<span style="color: rgba(0, 0, 0, 1)"> useContext(ThemeContext)
    const style </span>=<span style="color: rgba(0, 0, 0, 1)"> {
      background: theme.background,
      color: theme.color,
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;&gt;
      &lt;input type="text" ref={domRef} /&gt;
      &lt;button style={style}&gt;<span style="color: rgba(0, 0, 0, 1)">
            {like} 👍
      </span>&lt;/button&gt;
      &lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> LikeButton</pre>
</div>
<p>&nbsp;</p>
<div>
<div>Hook规则:</div>
<div>1.只在最顶层使用Hook,不要在循环和条件语句中使用hook</div>
<div>2.只在react函数中调用hook</div>
<div>&nbsp;</div>
<div>Hooks 的相关网站:https://zh-hans.reactjs.org/docs/hooks-intro.html</div>
<div><em>Hook</em>&nbsp;是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。</div>
<div>&nbsp;</div>
</div>
</div><br><br>
来源:https://www.cnblogs.com/xiaozhumaopao/p/12636559.html
頁: [1]
查看完整版本: 使用react搭建组件库(二):react+typescript