surmon.me.native代码分析笔记

来源:互联网 发布:淘宝互刷群沈阳 编辑:程序博客网 时间:2024/05/17 05:55

surmon.me.native代码分析笔记

surmon.me.native是一个适合入门学习的react-native博客内容展示项目,代码组织优良值得借鉴。

基础样式

作者把基础样式进行了统一编写,方便维护修改。

style/size.js

import { Dimensions, Platform } from 'react-native';const { width, height } = Dimensions.get('window');const screenHeight = width < height ? height : width;const screenWidth = width < height ? width : height;export default {  // Window Dimensions  screen: {    height: screenHeight,    width: screenWidth,    widthHalf: screenWidth * 0.5,    widthThird: screenWidth * 0.333,    widthTwoThirds: screenWidth * 0.666,    widthQuarter: screenWidth * 0.25,    widthThreeQuarters: screenWidth * 0.75,  },  // Navbar  navbarHeight: (Platform.OS === 'ios') ? 50 : 50,  statusBarHeight: (Platform.OS === 'ios') ? 16 : 24,  // Padding  padding: 20};

style/fonts.js

import { Platform } from 'react-native';function lineHeight(fontSize) {  const multiplier = (fontSize > 20) ? 0.1 : 0.33;  return parseInt(fontSize + (fontSize * multiplier), 10);}const base = {  fontSize: 14,  lineHeight: lineHeight(14),  ...Platform.select({    ios: {      // fontFamily: 'HelveticaNeue',    },    android: {      fontFamily: 'Roboto',    },  }),};export default {  base: { ...base },  h1: { ...base, fontSize: base.fontSize * 1.75, lineHeight: lineHeight(base.fontSize * 2) },  h2: { ...base, fontSize: base.fontSize * 1.5, lineHeight: lineHeight(base.fontSize * 1.75) },  h3: { ...base, fontSize: base.fontSize * 1.25, lineHeight: lineHeight(base.fontSize * 1.5) },  h4: { ...base, fontSize: base.fontSize * 1.1, lineHeight: lineHeight(base.fontSize * 1.25) },  h5: { ...base },};

欢迎页 welcome.js

编写欢迎页面提高用户体验,据说可以防止启动白屏。

// Init Layoutimport Layout from './layout.js';// Stylesimport { AppColors, AppSizes } from '@app/style';const styles = StyleSheet.create({  container: {    flex: 1,    backgroundColor: AppColors.background,     alignItems: 'center',     justifyContent: 'center'  },  launchImage: {    position: 'absolute',     left: 0,     top: 0,     width: AppSizes.screen.width,     height: AppSizes.screen.height  }});class Welcome extends Component {  componentWillMount () {    var navigator = this.props.navigator;    setTimeout (() => {      navigator.replace({component: Layout, passProps: { navigator }});    }, 1666);  }  render () {    return (      <View style={styles.container}>        <StatusBar            translucent={true}            backgroundColor={'#rgba(0, 0, 0, 0)'}            barStyle="light-content"            showHideTransition='slide'            hidden={false}        />        <Image style={styles.launchImage} source={require('@app/images/android-launch/launch-image.png')}></Image>      </View>    );  }}export default Welcome;

菜单页 menu.js

作者实现了安卓版本的屏幕左划出效果,编写子组件MneuHeader(菜单头部),MenuList(菜单列表)、MenuItem(菜单列表项)组合成Menu组件。

class Menu extends Component {  constructor(props) {    super(props);    this.state = {      selectedItem: props.initialEntry || props.entries[0].id//默认指向第一个列表    }  }  //点击菜单任意列表时,关闭菜单  _onSectionChange = (section) => {    this.setState({selectedItem: section});    this._drawer.closeDrawer();  }  //打开菜单  _openMenu = () => {    this._drawer.openDrawer();  }  //渲染菜单  _renderNavigationView = () => {    return (      <View style={this.props.containerStyle}>        <MneuHeader userInfo={this.props.userInfo}/>        <MenuList          items={this.props.entries}          selectedItem={this.state.selectedItem}          tintColor={this.props.tintColor}          onSectionChange={this._onSectionChange}        />      </View>    )  }  //渲染菜单内容  _renderContent() {    const element = this.props.entries.find(entry => entry.id === this.state.selectedItem).element;    if (element) {      return React.cloneElement(element, {openMenu: this._openMenu});    }  }  //使用DrawerLayoutAndroid组件,渲染菜单  render() {    return (      <DrawerLayoutAndroid        ref={(ref) => {this._drawer = ref}}        {...this.props}        renderNavigationView={this._renderNavigationView}>        {this._renderContent()}      </DrawerLayoutAndroid>    )  }}export default Menu;

API

作者将网络请求进行封装,增加了API的复用性。

import showToast from '@app/utils/toast';// apiconst baseApi = 'https://api.surmon.me';const fetchService = (url, options = {}) => {  return fetch(url, options)  .then(response => {    return response.json();  })  .then(json => {    showToast(json.message);    return json;  })  .catch(error => {    showToast('网络错误');    console.warn(error);  });};// apisexport default class Api {  // 获取文章列表  static getArticleList(page) {    const queryParams = page ? `?page=${page}` : '';    return fetchService(`${baseApi}/article${queryParams}`);  }  // 获取文章详情  static getArticleDetail(article_id) {    return fetchService(`${baseApi}/article/${article_id}`);  }  // 给文章或主站点赞  static likeArticleOrSite(like_data) {    return fetchService(`${baseApi}/like`, {      method: 'POST',      headers: {        'Accept': 'application/json',        'Content-Type': 'application/json',      },      body: JSON.stringify(like_data)    })  }  // 获取用户信息  static getUserInfo() {    return fetchService(`${baseApi}/auth`)  }}

Pages

程序主要包含文章列表页、文章内容页、项目页、关于页等4个页面。

文章列表页:

class ArticleList extends Component {  constructor(props) {    super(props);    this.state = {      loading: false,      firstLoader: true    };    // 获取本地存储的点赞记录    AsyncStorage.getItem('user_like_history')    .then(historyLikes => {      this.historyLikes = historyLikes ? JSON.parse(historyLikes) : []    }).catch(err => {      console.log(err)    })  }  // 文章列表项目渲染  renderRowView(article, sectionID, rowID) {    let liked = false;    if (this.historyLikes && this.historyLikes.length && this.historyLikes.includes(article.id)) {      liked = true;    }    return (      <ArticleListItem article={article}                        rowID={rowID}                       liked={liked}                       key={`sep:${sectionID}:${rowID}`}                        navigator={this.props.navigator} />    )  }  // 请求文章数据  getArticles(page = 1, callback, options) {    this.setState({ loading: true });    Api.getArticleList(page)    .then(data => {      this.setState({ loading: false, firstLoader: false });      const pagination = data.result.pagination;      callback(data.result.data, {        allLoaded: pagination.total_page < 2 || pagination.current_page >= pagination.total_page,      });    })    .catch(err => {      console.log(err);    });  }  // 在第一次读取时没有行显示时呈现视图,refreshcallback函数的函数调用刷新列表  renderEmptyView(refreshCallback) {    return (      <View style={customListStyles.defaultView}>        <Text style={customListStyles.defaultViewTitle}>暂无数据,下拉刷新重试</Text>        <TouchableHighlight underlayColor={AppColors.textDefault} onPress={refreshCallback}>          <Ionicon name="md-refresh" size={22} style={{color: AppColors.textDefault}}/>        </TouchableHighlight>      </View>    );  }  // 翻页正常状态  renderPaginationWaitingView(paginateCallback) {    return (      <TouchableHighlight          onPress={paginateCallback}          underlayColor={AppColors.textMuted}          style={customListStyles.paginationView}>        <Text style={          [customListStyles.actionsLabel, {             fontSize: AppFonts.base.fontSize,             color: AppColors.textDefault          }]        }>加载更多</Text>      </TouchableHighlight>    );  }  // 翻页在请求时的状态  renderPaginationFetchingView() {    return (      <View style={[customListStyles.paginationView, { backgroundColor: 'transparent' }]}>        <AutoActivityIndicator size={'small'} />      </View>    )  }  // 翻页在文章全部加载完时的状态  renderPaginationAllLoadedView() {    return (      <View style={[customListStyles.paginationView, {        height: AppSizes.padding * 1.5,        marginBottom: AppSizes.padding / 2,        backgroundColor: AppColors.background       }]}>         <Text style={          [customListStyles.actionsLabel, {             fontSize: AppFonts.base.fontSize,             color: AppColors.textMuted          }]        }>到底啦~</Text>      </View>    )  }  render(){    return (      <View style={styles.listViewContainer}>        <GiftedListView          style={styles.ArticleListView}          firstLoader={true}          initialListSize={10}          withSections={false}          enableEmptySections={true}          rowView={this.renderRowView.bind(this)}          onFetch={this.getArticles.bind(this)}          rowHasChanged={(r1,r2) => { r1.id !== r2.id }}          emptyView={this.renderEmptyView}          refreshable={true}          refreshableTitle={'更新数据...'}          refreshableTintColor={AppColors.brand.black}          refreshableColors={[AppColors.brand.primary]}          pagination={true}          paginationFetchingView={this.renderPaginationFetchingView}          paginationWaitingView={this.renderPaginationWaitingView}          paginationAllLoadedView={this.renderPaginationAllLoadedView}        />        {          this.state.firstLoader          ?  <View style={styles.ArticleListIndicator}>              <AutoActivityIndicator />              {                Platform.OS == 'ios'                ? <Text style={styles.ArticleListIndicatorTitle}>数据加载中...</Text>                : null              }            </View>          : null        }      </View>    )  }}export default ArticleList;

文章内容页使用react-native-simple-markdown组件展示文章内容。

// componentclass Detail extends Component {  constructor(props) {    super(props);    this.state = {      loading: false,      article: this.props.article,      articleContent: '',      liked: false    }    // 获取本地存储记录    AsyncStorage.getItem('user_like_history')    .then(historyLikes => {      this.historyLikes = historyLikes ? JSON.parse(historyLikes) : []    }).catch(err => {      console.log(err)    })  }  // 组件加载完成  componentDidMount() {    BackAndroid.addEventListener('hardwareBackPress', HandleBackBtnPress.bind(this));    this.setState({loading: true});    Api.getArticleDetail(this.props.article.id).then(data => {      this.setState({        loading: false,        article: data.result,        articleContent: data.result.content      })      if (this.historyLikes.length) {        if (this.historyLikes.includes(this.state.article.id)) {          this.setState({ liked: true });        }      }    }).catch(err => {      console.log(err);    })  }  // 喜欢文章  likeArticle() {    if (this.state.liked) return false;    Api.likeArticleOrSite({      type: 2,      id: this.props.article.id    }).then(data => {      this.state.liked = true;      this.state.article.meta.likes += 1;      this.forceUpdate();      this.historyLikes.push(this.state.article.id);      AsyncStorage.setItem('user_like_history', JSON.stringify(this.historyLikes))    }).catch(err => {      console.log(err);    })  }  // 去留言板  toComment() {    Alert.alert('功能还没做');  }  // 组件即将释放  componentWillUnmount() {    BackAndroid.removeEventListener('hardwareBackPress', HandleBackBtnPress.bind(this));  }  render() {    const { detail, article, loading, articleContent } = this.state;    const _navigator = this.props.navigator;    return (      <View style={{flex: 1, backgroundColor: '#fff'}}>        <ScrollView style={{height: AppSizes.screen.height - 200}}>          <Image source={buildThumb(article.thumb)} style={styles.image}>            <View style={styles.innerImage}>              <Text style={styles.title}>{ article.title }</Text>              <View style={styles.meta}>                { article.category.length                   ? <View style={[styles.metaItem, styles.metaItemLeft]}>                      <CommunityIcon name="book-open-variant" size={17} style={[styles.metaIcon, styles.metaText]}/>                      <Text style={styles.metaText}>{ String(article.category.map(c => c.name).join('、')) }</Text>                     </View>                   : null                }                <View style={[styles.metaItem, styles.metaItemLeft]}>                  <CommunityIcon name="eye" size={17} style={[styles.metaIcon, styles.metaText]}/>                  <Text style={styles.metaText}>{ article.meta.views }</Text>                </View>                <View style={styles.metaItem}>                  <CommunityIcon name="clock" size={17} style={[styles.metaIcon, styles.metaText, styles.metaDateIcon]}/>                  <Text style={styles.metaText}>{ toYMD(article.create_at) }</Text>                </View>              </View>            </View>          </Image>          { loading               ? <AutoActivityIndicator style={styles.indicator}/>              : <View style={styles.content}>                  <Markdown styles={markdownStyles}                   rules={markdownRules}                  blacklist={['list']}>{articleContent}</Markdown>                </View>          }          <NavBar leftOn={true}                  navigator={this.props.navigator}                  containerStyle={{backgroundColor: 'transparent'}} />        </ScrollView>        <View style={styles.footer}>          <TouchableOpacity style={styles.footerItem} onPress={this.toComment}>            <Icon name="comment" size={17} style={styles.footerItemIcon}/>            <Text style={styles.footerItemIconText}>{ `评论 (${article.meta.comments})` }</Text>          </TouchableOpacity>          <TouchableOpacity style={styles.footerItem} onPress={this.likeArticle.bind(this)}>            <Icon name={this.state.liked ? 'favorite' : 'favorite-border'}                   size={17}                   style={[styles.footerItemIcon, {                    color: this.state.liked ? 'red' : AppColors.textTitle                  }]}/>            <Text style={[styles.footerItemIconText, {                    color: this.state.liked ? 'red' : AppColors.textTitle                  }]}>{ `${this.state.liked ? '已' : ''}喜欢 (${article.meta.likes})` }</Text>          </TouchableOpacity>        </View>      </View>    )  }}export default Detail;

项目页通过WebView组件链接到作者的github仓储目录下。

class Projects extends Component {  constructor(props) {    super(props);    this.state = {      loading: true,      canGoBack: false    };  }  componentDidMount() {    BackAndroid.addEventListener('hardwareBackPress', this.handleBackAction.bind(this));  }  componentWillUnmount() {    BackAndroid.removeEventListener('hardwareBackPress', this.handleBackAction.bind(this));  }  // webview状态改变  onNavigationStateChange(navState) {    this.setState({ canGoBack: navState.canGoBack });    // console.log(this.refs.WEBVIEW_REF);  }  // 返回上一页Webview  backWebViewPrevPage() {    this.refs.WEBVIEW_REF.goBack();  }  // 刷新当前webview  reloadWebView() {    this.refs.WEBVIEW_REF.reload();  }  handleBackAction() {    // webview有回退页则返回    if (this.state.canGoBack) {      this.backWebViewPrevPage();    // 否则执行路由返回解析    } else {      HandleBackBtnPress.bind(this)();    }  }  render() {    return (      <View style={styles.container}>        <WebView style={styles.webview}                  ref="WEBVIEW_REF"                 onNavigationStateChange={this.onNavigationStateChange.bind(this)}                 source={{uri:'https://github.com/surmon-china?tab=repositories'}}                 onLoad={() => this.setState({loading: true})}                 onLoadEnd={() => this.setState({loading: false})}                 startInLoadingState={true}                 domStorageEnabled={true}                 javaScriptEnabled={true}        />        <NavBar leftOn={true}                 title={this.props.title}                 leftIsBack={Platform.OS === 'ios' && this.state.canGoBack}                onLeftPress={                  Platform.OS === 'ios'                   ? this.handleBackAction.bind(this)                  : this.props.openMenu                }                rightOn={true}                rightText={                  Platform.OS === 'ios'                   ? <Ionicons name='ios-refresh' size={32} color={AppColors.textPrimary} />                  : <Ionicons name='md-refresh' size={24} color={AppColors.textPrimary} />                }                onRightPress={this.reloadWebView.bind(this)}                />      </View>    )  }}export default Projects;

关于页面代码相对简单。

class About extends Component {  constructor(props) {    super(props);  }  componentDidMount() {    BackAndroid.addEventListener('hardwareBackPress', HandleBackBtnPress.bind(this));  }  componentWillUnmount() {    BackAndroid.removeEventListener('hardwareBackPress', HandleBackBtnPress.bind(this));  }  openSocial(url) {    Linking.openURL(url).catch(error => console.warn('An error occurred: ', error))  }  render() {    const userInfo = this.props.userInfo;    return (      <View style={styles.container}>        <View style={[styles.container, styles.userContent]}>          <Image style={styles.userGravatar} source={              userInfo ? {uri: userInfo.gravatar} : require('@app/images/gravatar.jpg')            }/>            <Text style={styles.userName}>{ userInfo ? userInfo.name : 'Surmon' }</Text>            <Text style={styles.userSlogan}>{ userInfo ? userInfo.slogan : 'Talk is cheap. Show me the code.' }</Text>          <View style={styles.userSocials}>            <TouchableOpacity style={styles.userSocialItem} onPress={() => { this.openSocial('https://github.com/surmon-china')}}>              <Ionicon name="logo-github" size={26} style={styles.userSocialIcon}/>            </TouchableOpacity>            <TouchableOpacity style={styles.userSocialItem} onPress={() => { this.openSocial('https://stackoverflow.com/users/6222535/surmon?tab=profile')}}>              <FontAwesome name="stack-overflow" size={22} style={styles.userSocialIcon}/>            </TouchableOpacity>            <TouchableOpacity style={styles.userSocialItem} onPress={() => { this.openSocial('https://weibo.com/nocower')}}>              <FontAwesome name="weibo" size={27} style={styles.userSocialIcon}/>            </TouchableOpacity>            <TouchableOpacity style={styles.userSocialItem} onPress={() => { this.openSocial('https://www.facebook.com/surmon.me')}}>              <Ionicon name="logo-facebook" size={30} style={styles.userSocialIcon}/>            </TouchableOpacity>            <TouchableOpacity style={styles.userSocialItem} onPress={() => { this.openSocial('https://twitter.com/surmon_me')}}>              <Ionicon name="logo-twitter" size={28} style={styles.userSocialIcon}/>            </TouchableOpacity>            <TouchableOpacity style={styles.userSocialItem} onPress={() => { this.openSocial('http://www.linkedin.com/in/surmon-ma-713bb6a2/')}}>              <Ionicon name="logo-linkedin" size={30} style={styles.userSocialIcon}/>            </TouchableOpacity>          </View>        </View>        <Navbar leftOn={true}                 title={this.props.title}                 onLeftPress={ () => {                  Platform.OS === 'android' && this.props.openMenu();                }}/>      </View>    )  }}export default About;