前面已經寫了一個 一個豆瓣電影小程序 的微信小程序;如今這個是 React+Typescript 的網頁版,基於 這裏 的修改版,antd 換爲 antd-mobilecss
小功能組件,好比倒計時、數量加減框、評分、搜索框等等 不涉及 異步請求、各類監聽(scroll等)的組件webpack
useMemo
能夠看成 computed
使用,useEffect
能夠實現 watch
的效果,也能夠有 mount/unmount
的效果,還有其餘方便的東西render
部分;public componentWillUnmount() {
// 組件銷燬後,不操做數據
this.setState = () => {};
}
複製代碼
函數+Hooks 寫法:ios
_onScroll
內 useState
只會起做用一次!!!很詭異(Capture Value ?)。。。 一開始我是這麼寫的,能夠在 這裏 看看git
Class 組件寫法:github
// src/views/home/index.tsx
constructor(props: IProps) {
super(props);
this._onScroll = this._onScroll.bind(this);
}
public componentDidMount() {
window.addEventListener('scroll', this._onScroll);
}
public componentWillUnmount() {
window.removeEventListener('scroll', this._onScroll);
}
複製代碼
因爲 React 沒有像 Vue 提供的 <keep-alive></keep-alive>
組件,要實現這個就本身動手來,這個在 這裏 已經大概說了一下web
這裏的 AuthRoute
是基於官方 Route 的封裝;主要就是使用 Route 的 render
方法 渲染列表頁,而後詳情頁是做爲 children
掛在列表頁下面的axios
// src/routes/home.tsx
import AuthRoute from '@/routes/auth-route';
import * as React from 'react';
import Loadable from '@loadable/component';
const Home = Loadable(() => import('@/views/home'));
const SearchList = Loadable(() => import('@/views/search-list'));
// home
export default [
<AuthRoute
key="search"
path="/search"
render={() => (
<SearchList>
<AuthRoute
exact={true}
path="/search/movie-detail/:id"
component={Loadable(() => import('@/views/movie-detail'))}
/>
</SearchList>
)}
/>,
<AuthRoute
key="home"
path="/"
render={() => (
<Home>
<AuthRoute
exact={true}
path="/movie-detail/:id"
component={Loadable(() => import('@/views/movie-detail'))}
/>
</Home>
)}
/>
]
複製代碼
this.props.children
就是上面 <Home>
裏面的東西// src/views/home/index.tsx
public isDetailPage() {
return this.props.location.pathname.includes("/movie-detail/");
}
public render() {
const {
movieLineStatus,
isLoading,
movieLine,
movieComing,
movieTop250,
isTop250FullLoaded
} = this.state;
return (
<div className={`${styles.home}`}>
{!this.isDetailPage() &&
<HeaderSearch onConfirm={(val) => this.onConfirm(val)} />
}
<div
className={`${styles['home-content']} center-content`}
style={{
display: this.isDetailPage()
? 'none'
: 'block'
}}>
<section className={styles['movie-block']}>
<div className={styles['block-title']}>
<span className={`${styles['title-item']} ${movieLineStatus === 0 && styles['title-active']}`}
onClick={() => this.movieStatusChange(0)}
>院線熱映</span>
<span className={`${styles['title-item']} ${movieLineStatus === 1 && styles['title-active']}`}
onClick={() => this.movieStatusChange(1)}
>即將上映</span>
</div>
{movieLineStatus === 0 ? (
<MovieItem movieList={movieLine} toDetail={(id: string) => this.toDetail(id)} />
) : (
<MovieItem movieList={movieComing} toDetail={(id: string) => this.toDetail(id)} />
)}
</section>
<MovieTop250 isLoading={isLoading} movieTop250={movieTop250} toDetail={(id: string) => this.toDetail(id)} />
{isLoading && <Loading />}
<TopBtn />
{isTop250FullLoaded && <div className={styles.nomore}>沒有更多數據了~</div>}
</div>
{/* detial */}
{ this.props.children }
</div>
)
}
複製代碼
scrollTop
;// src/views/home/index.tsx
constructor(props: IProps) {
super(props);
this._onScroll = this._onScroll.bind(this);
}
public componentDidMount() {
this._getMovieLine();
this._getMovieTop250();
getMovieTop250All();
this.props.history.listen(route => {
this.onRouteChange(route);
})
window.addEventListener('scroll', this._onScroll);
}
public componentWillUnmount() {
// 組件銷燬後,不操做數據
this.setState = () => {};
window.removeEventListener('scroll', this._onScroll);
}
// 監聽路由變化
public onRouteChange(route: any) {
// 首頁
if (route.pathname === '/') {
const { scrTop } = this.state;
window.addEventListener('scroll', this._onScroll);
// 恢復滾動條位置
this.setScrollTop(scrTop);
}
// 詳情頁
if (route.pathname.includes("/movie-detail/")) {
// 重置滾動條位置
this.setScrollTop(0);
window.removeEventListener('scroll', this._onScroll);
}
}
// 設置滾動條位置
public setScrollTop(top: number) {
document.body.scrollTop = top;
document.documentElement.scrollTop = top;
}
public _onScroll() {
const winHeight = window.innerHeight;
const srcollHeight = document.documentElement.scrollHeight;
const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
const toBottom = srcollHeight - winHeight - scrollTop;
if (toBottom <= 200) {
this._getMovieTop250({ start: this.state.currentPage*10 });
}
if (this.props.location.pathname === '/') {
this.setState({ scrTop: scrollTop });
} else {
window.removeEventListener('scroll', this._onScroll);
}
}
複製代碼
webpack v4.6.0+ 的功能,文檔小程序
在首頁路由,瀏覽器空閒時下載代碼,從首頁進入詳情頁時直接從緩存中讀取,沒有白屏微信小程序
使用如:
const Detail = Loadable(() => import(/* webpackPrefetch: true */ '@/views/movie-detail'));
複製代碼
路由:
// src/routes/home.tsx
import AuthRoute from '@/routes/auth-route';
import * as React from 'react';
import Loadable from '@loadable/component';
const Home = Loadable(() => import('@/views/home'));
const SearchList = Loadable(() => import('@/views/search-list'));
const Detail = Loadable(() => import(/* webpackPrefetch: true */ '@/views/movie-detail'));
// home
export default [
<AuthRoute
key="search"
path="/search"
render={() => (
<SearchList>
<AuthRoute
exact={true}
path="/search/movie-detail/:id"
component={Detail}
/>
</SearchList>
)}
/>,
<AuthRoute
key="home"
path="/"
render={() => (
<Home>
<AuthRoute
exact={true}
path="/movie-detail/:id"
component={Detail}
/>
</Home>
)}
/>
]
複製代碼
根據父元素的內容位置定位,會被限制在 padding
內,能夠用 margin
負邊距或者 transform
等改變位置;
父元素有 padding: 10px 20px;
,子元素設置 position: sticky; bottom: 0; left: 100%;
,可是會被限制在 padding
的範圍內,原來是使用 bottom: 0; rihgt: 0;
的,可是 right: 0;
不起做用。。。因此用 margin-right: -10px
修改一下位置
CSS.supports('position', 'sticky')
能夠判斷瀏覽器是否支持position: sticky;
<TopBtn />
樣式:
// src/components/scrollToTop/scrollToTop.scss
.top-btn {
width: 50px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
bottom: 20px;
border-radius: 100px;
border: 1px solid #eee;
background: #fff;
box-shadow: 0 2px 10px -1px rgba(0, 0, 0, 0.1);
z-index: 9;
&:active {
background: #eee;
}
}
.top-btn-fixed {
position: fixed;
right: 20px;
@extend .top-btn;
}
.top-btn-sticky {
position: sticky;
left: 100%;
margin-right: -10px;
@extend .top-btn;
}
複製代碼
<TopBtn />
組件:
// src/components/scrollToTop/index.tsx
import * as React from 'react';
import styles from './scrollToTop.scss';
const { useState, useEffect } = React;
/** * scrollToTop */
function scrollToTop() {
const [showBtn, setShowBtn] = useState(false);
useEffect(() => {
const height = window.innerHeight;
// 滾動距離大於一屏高度則顯示,不然隱藏
setShowBtn(() => (
document.body.scrollTop >= height
|| document.documentElement.scrollTop >= height
));
}, [document.body.scrollTop, document.documentElement.scrollTop]);
function toTop() {
if (window.scroll) {
window.scroll({ top: 0, left: 0, behavior: 'smooth' });
} else {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
}
return (
<div className={ CSS.supports('position', 'sticky') ? styles['top-btn-sticky'] : styles['top-btn-fixed'] } style={{visibility: showBtn ? 'visible' : 'hidden'}} onClick={toTop} > <i className="iconfont icon-arrow-upward-outline" /> </div> ); } export default scrollToTop; 複製代碼
其餘的沒什麼,項目自己也不復雜;框架用的是以前搭的 React+Typescript+antd-mobile,axios/css-modules/sass 等等這些都是標配啦;東西很少,原來的是 antd 這個是移動端因此換成 antd-mobile