توسعه یک اپلیکیشن هواشناسی با React.js - بخش دوم

توسعه یک اپلیکیشن هواشناسی با React.js - بخش دوم
در این فصل شروع به نوشتن اولین اپلیکیشن خود با React.js می‌کنیم. همچنین یاد می‌گیریم که چطور می‌توان اپلیکیشن را ایجاد و داده‌ها را از یک API واکشی کرد.
اپلیکیشن واقعی که قصد ایجاد آن را داریم، یک اپلیکیشن آب‌وهوا است که شرایط کنونی و پیش‌بینی هفت‌روز هفته را نمایش می‌دهد.
می‌توانید نمونه‌ی ارائه شده را در مرورگر خود، از طریق لینک زیر مشاهده کنید:
blog.mxstbr.com/weather-app
گوسفند زنده
گوسفند زنده
آموزش برنامه نویسی موبایل
آموزش برنامه نویسی اندروید به همراه پشتیبانی رایگان برای رفع خطاهای شما
خودتان را اینجا معرفی کنید

پیش­نیازها

 Node.js یک زمان‌اجرای جاوااسکریپت برای ترمینال شما می­باشد. این تکنولوژی را می­توان به وسیله‌ی برخی ابزارها که برای ایجاد اپلیکیشن خود استفاده خواهیم کرد، به‌کار برد. اگر قبلا نصب نکرده باشید (با اجرای دستور node –v در ترمینال خود چک کنید)، به سایت nodejs.org مراجعه کرده و طبق دستورالعمل ارائه‌شده، آخرین نسخه‌ی مربوطه (نسخه v6 در زمان نوشتن این مقاله) را نصب کنید.

گام اول

اخیرا فیسبوک، منبع باز یک ابزار کوچک خوب به نام create-react-app ارائه کرده، که امکان شروع راحت با اپلیکیشن React را برای ما فراهم می­کند. هم‌چنین شامل تمامی ابزارهای ساخت ضروری و گام­های نفوذ، جهت انجام کارها می­باشد.
این اپلیکیشن با دستور npm به‌صورت زیر نصب می­شود:
npm install -g create-react-app

به‌محض اتمام نصب، می­توانید به دستور create-react-app در پایانه‌ی خود دسترسی داشته باشید. در ادامه اپلیکیشن آب‌وهوا را به‌صورت اساسی ایجاد می‌کنیم:
create-react-app weather-app


آرگومان create-react-app که در نمونه‌ی ما weather-app هست، نام پوشه­ای که قرار است ایجاد شود را برای برنامه مشخص می­کند. از آن‌جایی که اپلیکیشن آب‌وهوا مدنظر می­باشد، بهتر است از همین نام استفاده شود.
اگر به فایل src/index.js مراجعه کنید، کد زیر را مشاهده خواهید کرد:
 

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

از آن‌جایی که create-react-app شامل یک سرور ساده است، بنابراین به‌جای باز کردن فایل index.html به‌صورت دستی، می­توان دستور npm run start را در مسیر weather-app اجرا کرده و اپلیکیشن خود را در localhost:3000 مشاهده کرد!
اگر به مؤلفه‌ی src/App.js مراجعه کنید، گروهی از کدهای استاندارد در آن مشاهده خواهید کرد. دستور import logo from './logo.svg'; (و فایل logo.svg در صورت دلخواه) و تمام JSXها را حذف کرده و به‌جای آن‌ها، یک عنوان با نام "Weather" ارائه دهید:
 

// App.js
import React from 'react';
import './App.css';

class App extends React.Component {
  render() {
    return (
      <h1> Weather </h1>
    );
  }
}

export default App;

اگر فایل مربوطه را ذخیره کرده و به مرورگرتان برگردید، عنوان "Weather" را مشاهده می­کنید. اما وظیفه‌ی import و export چیست؟

ماژول­ها

اپلیکیشن­های دنیای واقعی می­توانند شامل هر تعداد مؤلفه باشند. قرار دادن همه‌ی این مولفه­ها در یک فایل عملی نیست؛ بنابراین آن‌ها را به‌صورت یک ماژول ایجاد می­کنیم. روش فوق این امکان را فراهم می­کند که اپلیکیشن­های‌مان به‌خوبی سازمان یافته و به‌راحتی بتوان با آن کار کرد. کاری که انجام داده شد، مؤلفه‌ی App نامیده می­شود که از ماژول react و App.css استفاده می­کند!
ماژول­ها را می­توان از طریق خارج کردن چیزی از فایل، مثل خارج کردن مؤلفه‌ی App در بالا ایجاد کرد. سپس این مؤلفه می­تواند به فایل دیگر با دستور import App from './path/to/App.js' وارد شود. (در واقع با مراجعه به فایل index.js، اگر در حال انجام باشد خواهید دید!)

ماژول­های Node

همان‌طور که اشاره شد، کاری که در زمان اجرای دستور npm install انجام داده شد، نصب یک ماژول می­باشد. این بدین معنی است که یک نفر ماژولی (مثل ماژول App ذکر شده) را در npm (Node Package Manager) قرار داده است که بعدا می­توان نصب کرده و در کد خود استفاده کنید!
روشی که از React استفاده کرده و اپلیکیشن خود را بدون ضمیمه کردن چیزی ایجاد می­کنیم، یک مزیت بزرگ در مورد فهمیدن آن‌چه در جریان است می‌باشد!
خب، به اپلیکیشن آب‌وهوای خودمان برگردیم.
به‌منظور اینکه اپلیکیشن مورد نظر خوب به نظر بیاد، به کمی طراحی نیاز داریم. از آن‌جایی که برایتان آماده کردیم، می­توانید روی React تمرکز کنید و به راحتی تمام فایل­های CSS موجود در App.css را  جایگزین کنید.
هم‌چنین در اپلیکیشن خود باید مکانی که اطلاعات آب‌وهوای آن نیاز است، مشخص باشد؛ از این‌رو، یک فرم با یک ورودی و برچسبی با عنوان"City, Country" اضافه می­کنیم!
 

// App.js
class App extends React.Component {
  render() {
    return (
      <div>
        <h1> Weather </h1>
        <form>
          <label> I want to know the weather for
            <input placeholder={"City, Country"} type="text" />
          </label>
        </form>
      </div>
    );
  }
}

همان­طور که در کد مشاهده می­کنید، برچسب input درون برچسب label قرار داده شده است. از این­رو، زمانی که کاربران روی label کلیک می­کنند، input جهت دریافت ورودی انتخاب می­شود!
زمانی که در ورودی چیزی وارد کنید و دکمه‌ی "Enter" را فشار دهید، صفحه‌ی مورد نظر به‌روز شده و هیچ اتفاقی رخ نمی­دهد. چیزی که واقعا می‌خواهیم انجام دهیم، واکشی داده­ها هنگام وارد کردن یک‌شهر و یک‌کشور می­باشد. از این­رو، یک کنترل‌کننده‌ی onSubmit به فرم و یک‌تابع fetchData به مؤلفه‌ی خود اضافه می­کنیم!
 

// App.js
class App extends React.Component {
  fetchData = (evt) => {
    evt.preventDefault();
    console.log('fetch data!');
  };

  render() {
    return (
      <div>
        <h1> Weather </h1>
        <form onSubmit={this.fetchData}>
          <label> I want to know the weather for
            <input placeholder={"City, Country"} type="text" />
          </label>
        </form>
      </div>
    );
  }
}

با اجرای evt.preventDefault() در تابع fetchData (چیزی که در زمان فشار دادن enter در فرم مربوطه فراخوانی می­شود)، به مرورگر می‌گوییم که صفحه‌ی موردنظر را به‌روزرسانی نکند و به‌جای آن، هر چیزی که می­خواهد را نادیده گرفته و هرچه که ما بگوییم انجام دهد. واکشی داده­ها هر زمان که فرم را ارسال کنید، بارها و بارها وارد سیستم کنسول می­شود. چگونه شهر و کشور وارد شده را به تابع مورد نظر وارد می­کنیم؟
با ذخیره کردن مقدار ورودی در حالت مؤلفه‌ی محلی خود، می­توان آن را از متد مربوطه به‌دست آورد. زمانی که این کار را انجام می­دهیم، ورودی ما به اصطلاح یک «ورودی کنترل شده» محسوب می­شود.
مکان وارد شده کنونی در this.state.location ذخیره شده و یک متد کاربردی به نام changeLocation به مؤلفه‌ی خود اضافه می‌کنیم که متن ورودی را onChange کرده و حالت مربوطه را با متن کنونی مقداردهی می­کند:
 

// App.js
class App extends React.Component {
  fetchData = (evt) => { /* … */ };

  changeLocation = (evt) => {
    this.setState({
      location: evt.target.value
    });
  };

  render() {
    return (
      <div>
        <h1> Weather </h1>
        <form onSubmit={this.fetchData}>
          <label> I want to know the weather for
            <input
              placeholder={"City, Country"}
              type="text"
              value={this.state.location}
              onChange={this.changeLocation}
            />
          </label>
        </form>
      </div>
    );
  }
}

همان­طور که در فصل اول اشاره شد، زمانی که چیزی در حالت محلی خود ذخیره شود باید به‌صورت کد زیر از قبل تعریف شود:
 

// App.js
class App extends React.Component {
  state = {
    location: ''
  };

  fetchData = (evt) => { /* … */ };

  changeLocation = (evt) => {
    this.setState({
      location: evt.target.value
    });
  };

  render() {
    return (
      <div>
        <h1> Weather </h1>
        <form onSubmit={this.fetchData}>
          <label> I want to know the weather for
            <input
              placeholder={"City, Country"}
              type="text"
              value={this.state.location}
              onChange={this.changeLocation}
            />
          </label>
        </form>
      </div>
    );
  }
}

در تابع fetchData، بعدا می­توان به this.state.location برای به‌دست آوردن مکان کنونی دسترسی پیدا کرد:
 

// App.js

class App extends React.Component {
  state = { /* … */ };

  fetchData = (evt) => {
    evt.preventDefault();
    console.log('fetch data for', this.state.location);
  };

  changeLocation = (evt) => { /* … */ };

  render() { /* … */ }
}

اکنون هر مکانی که وارد کنید بایستی «واکشی داده برای MyCity, MyCountry» وارد شود.

واکشی داده­ها

در این بخش به معرفی واکشی داده­ها می­پردازیم. به‌جای کنسول ورود یک متن، به برخی اطلاعات آب‌و‌هوا نیاز داریم. برای این کار، از  OpenWeatherMap API استفاده می­شود که یک سرویس رایگان، جهت دسترسی به داده­های تمام مکان­های موجود در کل جهان را فراهم می­کند. از این‌رو یک کلید API نیاز دارید، پس به آدرس openweathermap.org/api رفته و در نوار بالا از طریق بخش "Sign Up" برای یک اکانت رایگان ثبت‌نام کنید.





به‌محض ثبت‌نام از طریق home.openweathermap.org/api_keys به صفحه‌کلید API خود رفته و کلیدAPI  را از آنجا کپی و در جایی امن نگهداری کنید.



اکنون که می­توان به کلیه‌ی اطلاعات آب‌و‌هوا دسترسی داشت، به اپلیکیشن خود ادامه می­دهیم!
درون تابع fetchData، باید یک درخواست جهت API بدهیم؛ از این‌رو می­خواهم از یک ماژول npm که xhr نامیده می­شود، استفاده کنیم که پوشه­ای می‌شود پیرامون JavaScript XMLHttpRequest سبب آسان شدن درخواست‌ها. دستور زیر را اجرا کنید:
 

npm install --save xhr

درحالی که ماژول مربوطه نصب می­شود، آن را در بالای مؤلفه‌ی API خود وارد کنید: 
 

// App.js
import React from 'react';
import './App.css';
import xhr from 'xhr';

class App extends React.Component { /* … */ };

برای دریافت داده­ها، ساختار URLای که درخواست خواهیم کرد مثل دستور زیر می­باشد:

http://api.openweathermap.org/data/2.5/forecast?q=CITY,COUNTRY&APPID=YOURAPIKEY&units=metric


CITY,COUNTRY را با شهر و کشور انتخابی، YOURAPIKEY را با کلید API کپی شده‌ی خود، جایگزین کنید. سپس URL فوق را در مرورگر خود باز کنید، که به‌صورت زیر می­باشد:

http://api.openweathermap.org/data/2.5/forecast?q=Vienna,Austria&APPID=asdf123&units=metric
آنچه شما دریافت خواهید کرد یک شئ JSON شامل ساختار زیر است: 
 

"city": {
  "id": 2761369,
  "name": "Vienna",
  "coord": {
    "lon": 16.37208,
    "lat": 48.208488
  },
  "country": "AT",
  "population": 0,
  "sys": {
    "population": 0
  }
},
"cod": "200",
"message": 0.0046,
"cnt": 40,
"list": [ /* Hundreds of objects here */ ]

آرایه‌ی list سطح بالا، شامل اطلاعات آب‌و‌هوای پنج‌روز آینده می­باشد که بر حسب زمان مرتب شده است. یکی از اشیاء آب‌و‌هوا به‌صورت زیر می­باشد: (تنها خطوط مربوطه نمایش داده شده است)
 

{
  "dt": 1460235600,
  "main": {
    "temp": 6.94,
    "temp_min": 6.4,
    "temp_max": 6.94
  },
  "weather": [
    {
      "main": "Rain",
      /* …more data here */
    }
  ],
  /* …more data here */
}

پنج‌ویژگی که برای ما اهمیت دارد عبارت‌اند از: dt_txt زمان پیش‌بینی آب‌و‌هوا، temp دمای مورد انتظار، temp_min و temp_max به‌ترتیب کم‌ترین و بیشترین دمای مورد انتظار و weather[0].main شرح آب‌و‌هوا در آن‌ زمان را مشخص می­کنند.OpenWeatherMap  اطلاعات بسیار زیادی که دور از تصور است، در اختیار ما قرار می­دهد. توصیه می­کنم کمی بیشتر اطراف را جستجو کرده و ببینید چه‌چیزی می­توان استفاده کرد تا اپلیکیشن جامع­تر شود.
اکنون که می­دانیم چه‌چیزی نیاز است، به این مسأله می­پردازیم که چگونه داده­ها را به‌طور واقعی واکشی کنیم؟ (در حال حاضر نصب  xhrبایستی به پایان رسیده باشد)
روال عمومی xhr به‌صورت زیر می­باشد:
 

xhr({
  url: 'someURL'
}, function (err, data) {
  /* Called when the request is finished */
});

همان­طور که مشاهده می­شود، تمام چیزهایی که واقعا برای ما اهمیت دارد ساخت URL و ذخیره کردن داده­های بازگشتی در محلی می­باشد.
می­دانیم که URL مورد نظر دارای یک پیشوند است که همیشه همان http://api.openweathermap.org/data/2.5/  forecast?q=و دارای یک پسوند که همیشه همان&APPID=YOURAPIKEY&units=metric می­باشد. تنها کاری که نیاز است انجام دهیم، افزودن مکانی است که کاربر به URL وارد می­کند!
هم‌چنین می­توانید واحدهایی که دوباره به‌دست آوردید را از طریق مقداردهی units با دستور زیر تغییر دهید:

imperial: http://api.openweathermap.org/data/2.5/forecast?q=something&APPID=YOURAPIKEY&units=imperial
اکنون اگر به این فکر می‌کنید که ممکن است کاربر در ورودی کاراکتر فاصله را وارد کند، آنگاه می‌دانید چه اتفاقی رخ خواهد داد؛ از آن‌جایی که URL با کاراکترهای فاصله معتبر نیست، درنتیجه کار نخواهد کرد و همه‌چیز متوقف می­شود. این موضوع درست است، اما جاوااسکریپت یک متد بسیار ساده برای رهایی از کاراکترهای غیر مجاز URL ارائه می­دهد. این متد encodeURIComponent() نامیده می‌شود و نحوه‌ی استفاده‌ی آن به‌صورت زیر است:

encodeURIComponent('My string with spaces'); // -> 'My%20string%20with%20spaces'
متد فوق را با ساختار  URL مورد نیاز، شرح xhr و حالت مؤلفه ترکیب ک و پس از کسب تمام اجزای مورد نیاز می­توان داده­ها را از سرور دریافت کرد.
لطفا توجه کنید که ممکن است نیاز داشته باشید سرور توسعه داده شده را دوباره راه­اندازی کنید (CTRL-C جهت توقف آن و npm start جهت آغاز دوباره) که برای این کار یک ماژول جدید نصب شده است!
ابتدا مکان از حالت مربوطه کدگذاری می­شود:
 

// App.js
import React from 'react';
import './App.css';
import xhr from 'xhr';

class App extends React.Component {
  state = { /* … */ };

  fetchData = (evt) => {
    evt.preventDefault();

    var location = encodeURIComponent(this.state.location);
  };

  changeLocation = (evt) => { /* … */ };

  render() { /* … */ }
}

سپس URL مورد نیاز که برای رهایی از مکان استفاده شده، ساخته می­شود:
 

// App.js
import React from 'react';
import './App.css';
import xhr from 'xhr';

class App extends React.Component {
  state = { /* … */ };

  fetchData = (evt) => {
    evt.preventDefault();

    var location = encodeURIComponent(this.state.location);

    var urlPrefix = 'http://api.openweathermap.org/data/2.5/forecast?q=';
    var urlSuffix = '&APPID=YOURAPIKEY&units=metric';
    var url = urlPrefix + location + urlSuffix;
  };

  changeLocation = (evt) => { /* … */ };

  render() { /* … */ }
}

آخرین مورد جهت دریافت داده­ها از سرور، فراخوانی xhr با URL فوق می­باشد:
 

// App.js
import React from 'react';
import './App.css';
import xhr from 'xhr';

class App extends React.Component {
  state = { /* … */ };

  fetchData = (evt) => {
    evt.preventDefault();

    var location = encodeURIComponent(this.state.location);

    var urlPrefix = 'http://api.openweathermap.org/data/2.5/forecast?q=';
    var urlSuffix = '&APPID=YOURAPIKEY&units=metric';
    var url = urlPrefix + location + urlSuffix;

    xhr({
      url: url
    }, function (err, data) {
      /* …save the data here */
    });
  };

  changeLocation = (evt) => { /* … */ };

  render() { /* … */ }
}

از آن‌جایی که به React جهت ارائه‌ی دوباره‌ی اپلیکیشن خود هنگام لود شدن داده­ها احتیاج داریم، باید آن را در حالت مؤلفه App خود ذخیره کنیم. هم‌چنین درخواست داده شبکه یک رشته می­باشد، از این‌رو باید آن را به وسیله‌ی JSON.parse به‌صورت یک شئ ایجاد کرد.
 

// App.js
import React from 'react';
import './App.css';
import xhr from 'xhr';

class App extends React.Component {
  state = { /* … */ };

  fetchData = (evt) => {
    evt.preventDefault();

    var location = encodeURIComponent(this.state.location);

    var urlPrefix = 'http://api.openweathermap.org/data/2.5/forecast?q=';
    var urlSuffix = '&APPID=YOURAPIKEY&units=metric';
    var url = urlPrefix + location + urlSuffix;

    var self = this;

    xhr({
      url: url
    }, function (err, data) {
      self.setState({
        data: JSON.parse(data.body)
      });
    });
  };

  changeLocation = (evt) => { /* … */ };

  render() { /* … */ }
}

نکته: var self = this; ضروری است، چرا که (== this) از تابع داخلی به‌مدت طولانی مؤلفه نبوده، بلکه تابع داخلی می­باشد.
داده‌ی مورد نظر را در حالت خود تعیین می­کنیم:
 

// App.js
import React from 'react';
import './App.css';
import xhr from 'xhr';

class App extends React.Component {
  state = {
    location: '',
    data: {}
  };

  fetchData = (evt) => { /* … */ };

  changeLocation = (evt) => { /* … */ };

  render() { /* … */ }
}

اکنون که اطلاعات آب‌و‌هوا برای مکان خواسته شده در حالت مؤلفه‌ی خود دریافت شده است، می­توان آن را در متد ارائه‌ شده‌ی خودمان به‌کار ببریم. یادتان باشد اطلاعات آب‌و‌هوای کنونی در آرایه‌ list، بر اساس زمان مرتب شده بود. بنابراین اولین عضو آرایه‌ی فوق، دمای کنونی می­باشد که ارائه داده می‌شود:
 

// App.js
import React from 'react';
import './App.css';
import xhr from 'xhr';

class App extends React.Component {
  state = { /* … */ };

  fetchData = (evt) => { /* … */ };
  changeLocation = (evt) => { /* … */ };

  render() {
    var currentTemp = 'not loaded yet';
    if (this.state.data.list) {
      currentTemp = this.state.data.list[0].main.temp;
    }
    return (
      <div>
        <h1> Weather </h1>
        <form onSubmit={this.fetchData}>
          <label> I want to know the weather for
            <input
              placeholder={"City, Country"}
              type="text"
              value={this.state.location}
              onChange={this.changeLocation}
            />
          </label>
        </form>
        <p className="temp-wrapper">
          <span className="temp">{ currentTemp }</span>
          <span className="temp-symbol"> °C </span>
        </p>
      </div>
    );
  }
}

در مرورگرتان این کار را انجام داده و مکان کنونی خود را وارد کنید تا دمای کنونی را مشاهده کنید. شگفت انگیز است!

خلاصه‌ی این فصل

در این فصل نحوه‌ی ایجاد اپلیکیشن خود را یاد گرفتیم و سپس یک ورودی متن کنترل شده ساخته و از آن جهت واکشی اولین داده‌ی خود از طریق xhr، استفاده کردیم.
اکنون که دمای کنونی در اختیار ماست، نیاز داریم پیش­بینی پنج‌روز آینده را ارائه دهیم. در فصل سه که افزودن یک نمودار پیش­بینی می­باشد، نحوه‌ی کشیدن یک نمودار را یاد خواهیم گرفت. 

منبع: plot.ly

پریسا شجاعی فارغ التحصیل رشته مهندسی کامپیوتر مقطع کارشناسی ارشد و مدرس و پژوهشگر در رشته‌های کامپیوتر و فناوری اطلاعات می‌باشم. ابتدا برنامه نویسی را با HTML و CSS شروع و سپس به یادگیری زبان‌های C# و SQL Server پرداخته و پروژه‌هایی در این زمینه انجام دادم. به خاطر علاقه‌ای که در زمینه طراحی وب داشتم سراغ حوزه Frontend رفته و در حال حاضر در حال یادگیری زبان های python و JavaScript می‌باشم. به روز بودن و افزایش مستمر اطلاعات از علایق اصلی من است.

نظرات و سوالات کاربران

ارسال پاسخ مسعود صدری
مسعود صدری
چهارشنبه ۲۴ خرداد ۱۳۹۶ ۰۰:۲۸
سلام.

ممنون که مطالعه می‌کنید. بله حتما به روزرسانی‌ها رو سعی می‌کنیم بهتر انجام بدیم.
ارسال پاسخ صادق خسروانجم
صادق خسروانجم
دوشنبه ۲۲ خرداد ۱۳۹۶ ۰۸:۱۹
ممنون از آموزش هاتون , یه انتقاد حالا که سایت هر فصلی یک بار آپدیت می شه , حداقل لینک های مفید روزانه رو , روزانه بروز کنید