First look at the effect of the overall app, source code GitHub code address, welcome to star and discuss together.


Yue on the weather

My code development environment information:

Mac OS X 10.13.2 IDE: Sublime Text, Version 3.0, Build 3143 Node version: V9.3.0 NPM version: 5.6.0 YARN version: React-native Version: 0.51.0 Xcode: Version 9.2 (9C40b) Android Studio 3.0.1Copy the code

Tools – To do a good job, you must sharpen your tools

My code editor of choice is Sublime Text (which I share with baidu as a link: Sublime Text 3, password: BMJS), and the main reason I chose it to develop React Native is that it’s free. However, to be efficient with Sublime development, you must download several efficient plug-ins using The Package Control plugin for Sublime: JSX(ES6) syntax highlighting plugin Babel, JSX code formatting plugin JsFormat, and JSX syntax checking plugin ESLint. These three are enough for my current code development.

I found another development toolExpo SnackVery powerful, you can write RN code directly in the browser and see the effect in real time to improve development efficiency. You can also publish the app to Expo for viewing, such as the learning demo of React navigationNavigationPlayground, beginners can try to use this tool for development.

  1. Install Package Control Tools -> Command Palette… -> Enter: Install, and then mouse click on the Package Control option. The Sublime editor should have text in the lower left corner indicating that it is downloading. Sublimetext -> Preferences Package Control at the bottom

  2. Install JSX syntax highlighting plugin: Babel

    Sublime before and after the JSX syntax highlighting plugin

    Now React Native uses JSX (ES6) syntax, sublime cannot be highlighted. The difference between the lower right corner of the left and the lower right corner of the right is that you can see the default JavaScript and JavaScript (Babel), which is the difference between JS and JSX syntax. But there is no Babel language option by default, so we need Package Control to download it.

    Installation steps:Shift+Command+P -> Enter: Install Package, Press Enter -> Enter: Babel, press Enter.

    Download the Babel

    Sublime Text -> Preferences -> Package Settings

    Babel download successful

    Switch the syntax to Babel and click on the language in the lower right corner of Sublime to switch to Babel and highlight the source file.

    Set the Babel

One problem I ran into was that when I closed the project, the next time I opened the js file, the default language was JavaScript, and I had to repeat the previous step to highlight it: switch the syntax to Babel. If who has once and for all setting method, please inform next!

  1. Install code formatting plug-in: JsFormat Install and view the same steps as in 2, using Package Control to search for JsFormat. First, the code formatting shortcut CTRL +option+ F. To prevent collisions with other plug-in shortcuts, we’d better add the following JSON to Key Binding-user
{ "keys": ["ctrl+alt+f"], "command": "js_format", "context": [{"key": "selector", "operator": "equal", "operand": "source.js,source.json"}] }
Copy the code


JsFormat set




Set the User Keybindings file



Second, we need to set up the code to format support for JSX, as shown aboveJsFormat setSo let’s go to settings-user

{"e4x": true, // support JSX "format_on_save": false, // save automatic code formatting, default is also false, can not add "brace_style": "Collapse -preserve-inline", // {collapse-preserve-inline}Copy the code

When I set the “format_on_save”: true, I noticed that some of the code that triggered formatting when the shortcut automatically saved the code was messed up, and only in the ListView section, as shown in the GIF below, so we can explore if anyone can fix this problem.


jsFormat.gif


  1. I opted for a local installation (installed in the current project directory /node_modules/), (if the global installation is used, use the global installation, In /usr/local/lib/node_modules), run the following commands in the root directory of the project:
$NPM install eslint --save-dev $NPM install eslint-plugin-react --save-dev # eslint supports the react semantics of JSX plug-in $NPM install $NPM install babel-eslint # install this plugin to support ES6 syntaxCopy the code

Note whether the UNMET PEER DEPENDENCY field appears during installation, if it does, the installation fails, then try global installation. 4.2 Configuration Switch the eslintrc file to the project root directory containing packge.json and run eslint –init


Eslint options



.eslintrc.json

{ "env": { "es6": true, "node": true, "react-native/react-native": true }, "extends": ["eslint:recommended", "plugin:react/recommended","plugin:react-native/all"], "plugins": [ "react", "react-native" ], "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 6, "ecmaFeatures": { "jsx": True}}, "react-native/no-color-literals": {// react-native syntax: class-methods-use-this": 0, "react-native/no-color-literals": 0, "react-native/no-unused-styles": 0, "react-native/no-inline-styles": 0, "react-native/split-platform-components": 2, "no-unused-vars": 0, "no-use-before-define": 0, "no-console": 0, "react/prop-types": 0, "react/no-direct-mutation-state": 0 } }Copy the code

For more detailed configurations, see Configuring ESLint. SublimeLinter, Sublimelinter-contrib-esLint was installed using package control, and the setting-user file was configured as follows:

{" user ": {" debug" : true, "delay" : 0.25, "error_color" : "D02000", "gutter_theme" : "Packages/SublimeLinter/gutter-themes/Default/Default.gutter-theme", "gutter_theme_excludes": [], "lint_mode": "background", "linters": { "eslint": { "@disable": false, "args": [], "excludes": [] }, "jsxhint": { "@disable": false, "args": [], "excludes": [] } }, "mark_style": "outline", "no_column_highlights_line": false, "passive_warnings": false, "paths": { "linux": [], "osx": [], "windows": [] }, "python_paths": { "linux": [], "osx": [], "windows": [] }, "rc_search_limit": 3, "shell_timeout": 10, "show_errors_on_save": false, "show_marks_in_minimap": true, "syntax_map": { "html (django)": "html", "html (rails)": "html", "html 5": "html", "javascript (babel)": "javascript", "magicpython": "python", "php": "html", "python django": "python", "pythonimproved": "python" }, "tooltip_fontsize": "1rem", "tooltip_theme": "Packages/SublimeLinter/tooltip-themes/Default/Default.tooltip-theme", "tooltip_theme_excludes": [], "tooltips": false, "use_current_shell_path": false, "warning_color": "DDB700", "wrap_find": true } }Copy the code


settings-user


The effect is as follows:




Static syntax checks for effects

Second, the code

Yueyue Weather this app is relatively simple (GitHub code address), mainly has two parts: city weather display interface, select the city interface. The interface of the home weather forecast uses the API of the wind weather free 1000 times a day, and the city list data uses the interface of the first line of code of Guo Lin. Below is a set of RN code in iPhone6 emulator and android emulator respectively effect, code reuse rate is almost 100%.


android_weather.gif




ios_weather.gif

It took me a week to develop this app, including learning the most basic JSX syntax of RN, components, state state and props. So far, I have completed the whole app. So far, I only know a little about it, but more practice and learning are needed for deeper understanding and use. So this part of the code I am more as a beginner to quickly code and solve problems. As for the learning courses, I suggest using the React Native website instead of the React Native Chinese website. However, this requires some Basic English knowledge. If you don’t understand React Native, you can use translation tools. For example, create-React-Native App, Expo and other technologies mentioned below.

  1. Create project: create-react-native app CREate-react-native app DailyWeather instead of react-native init DailyWeather The biggest difference between the two is that the former is not embedded, so the project can be run directly by NPM start or YARN Start. Combined with Expo APP, the running effect can be checked in the simulator in real time. There is no ios and Android file directory, a pure JS project. You cannot open the project for configuration through Xcode or Android Studio development tools. Since the icon icon and startup image of my app are in the native configuration, I need to run NPM run eject or YARN run eject to generate the two ios and Android directories. Then configure the icon icon and launch page by configuring Xcode and Android Studio.

    Create-react-native app DailyWeather project directory

    React-native Init DailyWeather and YARN Run eject project directory

  2. Before implementing YARN Run eject in the project directory (that is, before there are ios and Android project directories), I directly executed YARN Start to start a development server, and then used Expo App to open Yueyue Weather App on the simulator or mobile phone for development and debugging. Then I opened the project directly through Xcode and Android Studio and ran it on the emulator, Before running AppDelegate. The first line 21 m code jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot: @ “index” fallbackResource:nil]; To jsCodeLocation = [NSURL URLWithString: @ “http://127.0.0.1:8081/index.bundle?platform=ios&dev=true”); Otherwise, an error will be reported:

  3. Home page coding


class HomeScreen extends Component < {} > {

  //组件的构造方法,在组建创建的时候调用
  constructor(props) {
    super(props);
    var ds = new ListView.DataSource({
      rowHasChanged: (r1, r2) => r1 !== r2
    });
    this.state = {
      isLoading: true,
      quiltyColor: true,
      refreshing: false,
      dataSource: ds,
      suggegstions: {}, // 代表一个空json,生活建议
      aqi: {}, // aqi
      basic: {},
      title: '海淀' // 默认获取海淀区的天气
    };
  }

  // 页面的渲染
  render() {
    if (this.state.isLoading) {
      return (
        <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
          <ActivityIndicator/>
        </View>
      )
    }
    return (
      <View style={{flex: 1}}>
        {/*北京图*/}
        <ImageBackground source={{uri: 'http://cn.bing.com/az/hprichbg/rb/BarHarborCave_ROW9345444229_1920x1080.jpg'}}  style={{width: screenWidth, height: screenHeight}}>
          <ScrollView style = {{flex: 1}} 
            refreshControl={
              <RefreshControl refreshing={this.state.refreshing} onRefresh={this._onRefresh.bind(this)}/>
            }>
            {/*渲染头部信息*/}
            {this.reanderHeader()}
            {/*渲染天气预报列表*/}
            {this.reanderForecast()}
            {/*渲染天气质量*/}
            {this.renderAirquilty()}
            {/*渲染生活指数*/}
            {this.renderSuggestion()}
          </ScrollView> 
        </ImageBackground>
      </View>
    );
  }

  _onRefresh() {
    this.setupData(this.state.title);
  }

  // 组件已经装载,绘制完毕调用
  componentDidMount() {
    console.log(screenWidth)
    console.log(screenHeight)
    this.setupData(this.state.title);
  }

  // 组件即将卸载,组件要被从界面上移除时调用
  componentWillUnmount() {
    console.log('HomeScreen','componentWillUnmount')
  }

  setupData(cityname) {
    // cityid可以使用cityid 也可以使用 城市名,选择城市回调回来
    let urlstr = 'http://guolin.tech/api/weather?key=41dd96cb90344217acbf5fe0813f16cd' + '&cityid=' + cityname
    console.log(urlstr)
    fetch(urlstr)
      .then((response) => response.json())
      .then((responseJson) => {
        console.log('success1', responseJson);
        console.log('success2', responseJson.HeWeather[0].daily_forecast);
        console.log('success3', responseJson.HeWeather[0].suggestion);

        this.setState({
          refreshing: false,
          isLoading: false,
          dataSource: this.state.dataSource.cloneWithRows(responseJson.HeWeather[0].daily_forecast),
          suggegstions: responseJson.HeWeather[0].suggestion,
          aqi: responseJson.HeWeather[0].aqi,
          title: responseJson.HeWeather[0].basic.city,
          des: responseJson.HeWeather[0].now.cond.txt,
          temp: responseJson.HeWeather[0].now.tmp,
        })
      })
      .catch((error) => {
        console.error(error);
      });
  }

  reanderHeader() {
    return (
      <View style={styles.header}>

            <View style={{flexDirection: 'row', alignItems: 'center' ,marginTop: 44, marginBottom: 10}}>
              <Text style={styles.headerTitle}>{this.state.title}</Text>
              <TouchableOpacity onPress={() => this.props.navigation.navigate('City',{name: this.state.title, currentLevel: 0, callBack: (data) => {
                  console.log(data) // weather_id
                  this.setupData(data);
                }})}>
                <Image source={require('./address.png')}/>
              </TouchableOpacity>
            </View>
            <Text style={styles.headerDes}>{this.state.des}</Text>
            <Text style={styles.headerTepe}>{this.state.temp}℃</Text>
      </View>
    );
  }

  reanderForecast() {
    return (
      <View style={styles.forecast}>
            <Text style={{color: 'white', fontSize: 20, marginBottom: 10}}>预报</Text>
            <ListView 
              dataSource={this.state.dataSource}
              renderRow={(rowData) =>  (
                <View style={styles.listView}>
                  <Text style={{color: 'white',flex: 1}}>{rowData.date}</Text>
                  <Text style={{color: 'white',flex: 1}}>{rowData.cond.txt_d}</Text>
                  <Text style={{color: 'white',flex: 1}}>{rowData.tmp.max}</Text>
                  <Text style={{color: 'white',flex: 1}}>{rowData.tmp.min}</Text>
                </View>
              )}/> 
      </View>
    );
  }

  renderAirquilty() {
    let {
      aqi
    } = this.state;
    return (
      <View>
          <View style={styles.suggestion}>
            <View style={{flexDirection: 'row',justifyContent: 'flex-start', alignItems: 'center',padding: 20}}>
              <Text style={{color: 'white',fontSize: 20}}>空气质量:</Text>
              <Text style={{color: 'red',fontSize: 17}}>{aqi.city.qlty}</Text>
            </View>
            <View style={{flex: 1,flexDirection: 'row'}}>
              <View style={{flex: 1, alignItems: 'center' }}>
                <Text style={styles.airQuiltyDes}>AQI指数</Text>
                <Text style={styles.airQuiltyValue}>{aqi.city.aqi}</Text>
              </View>
              <View style={{flex: 1, alignItems: 'center' }}>
                <Text style={styles.airQuiltyDes}>PM2.5</Text>
                <Text style={styles.airQuiltyValue}>{aqi.city.pm25}</Text>
              </View>
            </View>
            <View style={{flex: 1,flexDirection: 'row'}}>
              <View style={{flex: 1,alignItems: 'center' }}>
                <Text style={styles.airQuiltyDes}>PM10</Text>
                <Text style={styles.airQuiltyValue}>{aqi.city.pm10}</Text>
              </View>
              <View style={{flex: 1, alignItems: 'center' }}>
                <Text style={styles.airQuiltyDes}>O3指数</Text>
                <Text style={styles.airQuiltyValue}>{aqi.city.o3}</Text>
              </View>
            </View>
          </View>
        </View>
    )
  }

  renderSuggestion() {
    let {
      suggegstions
    } = this.state;
    return (
      <View style={styles.suggestion}>
          <Text style={{color: 'white',fontSize: 20,marginBottom: 20,marginTop: 20,marginLeft: 20}}>生活建议</Text>

          <Text style={styles.suggestionDes}>空气质量:{suggegstions.air.txt}</Text>
          <Text style={styles.suggestionDes}>舒适度:{suggegstions.comf.txt}</Text>
          <Text style={styles.suggestionDes}>洗车:{suggegstions.cw.txt}</Text>
          <Text style={styles.suggestionDes}>穿衣:{suggegstions.drsg.txt}</Text>
          <Text style={styles.suggestionDes}>感冒:{suggegstions.flu.txt}</Text>
          <Text style={styles.suggestionDes}>运动:{suggegstions.sport.txt}</Text>
          <Text style={styles.suggestionDes}>旅游:{suggegstions.trav.txt}</Text>
          <Text style={styles.suggestionDes}>紫外线:{suggegstions.uv.txt}</Text>
        </View>
    );
  }
}
Copy the code

We’ll focus on the Render () method, which renders the page. To be honest, the Flexbox layout was a bit awkward at first (using iOS layout technology AutoLayout for a long time), but eventually it took a few demos to get the basics right. The ImageBackground image uses the ImageBackground component, the home page information display uses the ScrollView component, the three-day forecast uses the ListView component, and the rest is nested with other components. Two obstacles were encountered: the background of the page originally used the Image component, but found that it could not nest other elements; Second is page routing, StackNavigator using React navigation, which I spent two days learning, including his official demo NavigationPlayground (you need to download Expo app first to scan the QR code to open).

  1. City list code


class CityScreen extends React.Component { static navigationOptions = ({navigation}) => ({ headerMode: 'float', gesturesEnabled: true, headerTitle: `${navigation.state.params.name}`, headerLeft: ( <TouchableOpacity onPress={() => { navigation.goBack(null) }}> <Image source={require('./moments_btn_back.png')} style={{marginLeft: 8}}/> </TouchableOpacity> ), headerStyle: { backgroundColor: '#6666ff', }, headerTintColor: 'white', headerTitleStyle: { fontSize: 20 } }); constructor(props) { super(props); this.state = { loading: true, currentLevel: 0, // 0:province, 1:city, 2:county; provinces: [{}], provinceData: {}, cityData: {} }; } render() { const { navigation } = this.props; const { state, setParams, goBack } = navigation; const { params, currentLevel } = state; if (this.state.loading) { return (<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}> <ActivityIndicator/> </View>) } return ( <FlatList style={{backgroundColor: 'white'}} data={this.state.provinces} renderItem={({item}) => { return ( <TouchableOpacity style={{justifyContent: 'center', alignItems: 'center',height: 44}} onPress={() => { setParams({name: item.name}) let level = this.state.currentLevel if (this.state.currentLevel === 0) { this.state.provinceData = item } else if (this.state.currentLevel === 1) { this.state.cityData = item } if (this.state.currentLevel === 2) { Console. log("goBack") state.params.callback (item.weather_id) // callBack goBack(null)} else {this.state.currentLevel = level  + 1 setParams({currentLevel: level + 1}) console.log(this.state.currentLevel) // console.log(this.state.provinceData ) this.state.loading = true this.setupData(this.state.currentLevel) } }}> <Text style={{ color: 'gray', fontSize: 20}} >{item.name} </Text> </TouchableOpacity> )} } ItemSeparatorComponent={ () => { return ( <View style={{height: 1, backgroundColor: '#eee'}}/> //</View> )}} keyExtractor={ (item, index) => item.id } /> ) } componentDidMount() { this.setupData(0) } setupData(level) { var urlstr = '' if (level == 0) { urlstr = 'http://guolin.tech/api/china' } else if (level == 1) { let provinceData = this.state.provinceData console.log(provinceData) urlstr = 'http://guolin.tech/api/china/' + `${provinceData.id}` } else if (level == 2) { let provinceData = this.state.provinceData let cityData = this.state.cityData urlstr = 'http://guolin.tech/api/china/' + `${provinceData.id}` + '/' + `${cityData.id}` } console.log(urlstr) fetch(urlstr) .then((response) => response.json()) .then((responseJson) => { console.log(responseJson) this.setState({ loading: false, provinces: responseJson, currentLevel: level, }) }) .catch((error) => { console.error(error); }); }}Copy the code

I use the FlatList component for the list. There are two obstacles to this page. One is to click item to replace the headerTitle of navigation. The second is the last click city callback data to the home page at the same time update home page data.

conclusion

You could say this app is simple, and it is (no maps, no data persistence, maybe in the future), just a simple interface presentation. But only when you actually practice developing your own app, you will find that the process is not as easy as you think; But when you’re done, you’ll feel like you’ve accomplished something, especially being able to share it and let more people see it. Two days ago, I saw the 2018 technology trend forecast by Chi Jianqiang, mobile development learning point JS is also very good.