На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
Модераторы: ElcnU, ANDLL, fatalist
  
> Изучаю React, modern style: ES7, Babel, webpack, bootstrap
    Изучаю React, а вы? :huh:
    Вот набросал примерчик может кому пригодится для изучения
    в нем я максимльно следовал современому стилю программирования на React

    Сам пока до конца не изучил все тонкости React, но думаю заброшу Backbone и перейду на React :D

    /public/index.html:
    ExpandedWrap disabled
      <!DOCTYPE html>
      <html>
       
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <title>React in action</title>
      </head>
       
      <body>
        <div id="app">
          React in action
        </div>
       
        <script src="/app.js" charset="utf-8"></script>
      </body>
       
      </html>


    /source/app.js:
    ExpandedWrap disabled
      import React from 'react';
      import {render} from 'react-dom';
      import Menu from './menu';
      import data from './data/recipes';
      import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
       
      const App = () =>
        <div className="container">
          <Menu recipes={data}/>
        </div>
       
      render(<App/>, document.getElementById('app'));


    /source/menu.js:
    ExpandedWrap disabled
      import React from 'react';
      import Recipe from './recipe';
       
      const Menu = ({recipes}) =>
        <article>
          <header>
            <h1>Delicious Recipes</h1>
          </header>
          <div className="recipes">
            {recipes.map((recipe, i) =>
              <Recipe key={i} {...recipe}/>)}
          </div>
        </article>;
       
      export default Menu;


    /source/recipe.js:
    ExpandedWrap disabled
      import React from 'react';
      import IngredientsList from './ingredients-list';
      import Instructions from './instructions';
       
      const Recipe = ({name, ingredients, steps}) =>
        <section id={name.toLowerCase().replace(/\s/g, '-')}>
          <h2>{name}</h2>
          <IngredientsList list={ingredients}/>
          <Instructions title="Cooking Instructions" steps={steps}/>
        </section>;
       
      export default Recipe;


    /source/ingredient-list.js:
    ExpandedWrap disabled
      import React from 'react';
      import Ingredient from './ingredient';
       
      const IngredientsList = ({list}) =>
        <ul className="ingredients">
          {list.map((ingredient, key) =>
            <Ingredient key={key} {...ingredient}/>)}
        </ul>;
       
      export default IngredientsList;


    /source/intructions.js:
    ExpandedWrap disabled
      import React from 'react';
       
      const Instructions = ({title, steps}) =>
        <section className="instructions">
          <h3>{title}</h3>
          {steps.map((step, i) =>
            <p key={i}>{step}</p>)}
        </section>;
       
      export default Instructions;


    /source/ingredient.js
    ExpandedWrap disabled
      import React from 'react';
       
      const Ingredient = ({amount, measurement, name}) =>
        <li>
          <span className="amount">{amount}</span>
          <span className="measurement">{measurement}</span>
          <span className="name">{name}</span>
        </li>;
       
      export default Ingredient;



    package.json:
    ExpandedWrap disabled
      {
        "name": "learn-react",
        "version": "1.0.0",
        "description": "",
        "private": true,
        "dependencies": {
          "axios": "^0.18.0",
          "bootstrap": "^4.1.1",
          "express": "^4.16.3",
          "react": "^16.3.2",
          "react-dom": "^16.3.2",
        },
        "devDependencies": {
          "babel-core": "^6.26.0",
          "babel-loader": "^7.1.4",
          "babel-plugin-transform-object-rest-spread": "^6.26.0",
          "babel-preset-env": "^1.6.1",
          "babel-preset-react": "^6.24.1",
          "css-loader": "^0.28.11",
          "nodemon": "^1.17.4",
          "style-loader": "^0.21.0",
          "webpack": "^4.8.3",
          "webpack-cli": "^2.0.15"
        },
        "scripts": {
          "start": "nodemon server.js",
          "build": "webpack --config webpack.config.js"
        },
        "author": "Mudosoft",
        "license": "UNLICENSED"
      }


    webpack.config.js:
    ExpandedWrap disabled
      const path = require('path');
       
      module.exports = {
        mode: 'development',
        entry: './source/app.js',
        output: {
          path: path.resolve(__dirname, 'public'),
          filename: 'app.js',
          sourceMapFilename: 'app.map'
        },
        devtool: '#source-map',
        module: {
          rules: [
            {
              test: /\.js$/,
              use: [
                {
                  loader: 'babel-loader',
                  options: {
                    presets: ['react', 'env'],
                    plugins: [
                      'transform-object-rest-spread'
                    ]
                  }
                }
              ],
              exclude: /(node_modules)/
            },
            {
              test: /\.css$/,
              use: [
                'style-loader',
                'css-loader'
              ]
            }
          ]
        }
      };


    Прикреплённый файлПрикреплённый файлreact_in_action.zip (4,46 Кбайт, скачиваний: 7)

    пс. чтобы все заработало надо выполнить команду npm install, далее npm run build, далее npm start, далее в броузере Chrome набрать localhost:3000 ВСЕ!
    Сообщение отредактировано: Cfon -
      Цитата Cfon
      думаю заброшу Backbone


      что, так много на нём уже сделал, что аж бросать приходится? :whistle:
      свободные и открытые Web-скрипты k313.net закончились...
        Цитата K313 @
        что, так много на нём уже сделал, что аж бросать приходится? :whistle:

        да много всего уже написано на Backbone ~10k строк гавнокода :D
          хотя Backbone ешо на плаву поскольку можно юзать его в связке с React
          надо будет переделать Simple Blog с React+Backbone для практики :D
            Итак продолжаю изучать React!
            добавил два компонента Summary и StarRating.
            в первом показано как в React работать с типами и значениями по умолчанию,
            второй вводит интерактивность в приложение, в связи в этим было введено состояние (state) у компонента App!

            смотрите и учитесь, ну или критикуйте :D

            summary.js:
            ExpandedWrap disabled
              import React, {Component} from 'react';
              import PropTypes from 'prop-types';
               
              class Summary extends Component {
                static propTypes = {
                  ingredientsCount: PropTypes.number,
                  stepsCount: PropTypes.number
                }
                static defaultProps = {
                  ingredientsCount: 0,
                  stepsCount: 0
                }
                render() {
                  const {ingredientsCount, stepsCount} = this.props;
                  return (
                    <div className="summary">
                      <p>{ingredientsCount} ingredients | {stepsCount} steps</p>
                    </div>
                  );
                }
              }
               
              export default Summary;


            star-rating.js:
            ExpandedWrap disabled
              import React from 'react';
              import '../css/star.css'
               
              const Star = ({selected=false, onClick=()=>{}}) =>
                <div className={selected ? 'star selected' : 'star'}
                  onClick={onClick}>
                </div>;
               
              const StarRating = ({starsSelected=0, totalStars=5, onRate=()=>{}}) =>
                <div className="star-rating">
                  {[...Array(totalStars)].map((n, i) =>
                    <Star key={i} selected={i < starsSelected} onClick={()=>onRate(i+1)}/>
                  )}
                  <p>{starsSelected} of {totalStars} stars</p>
                </div>;
               
              export default StarRating;


            recipe.js:
            ExpandedWrap disabled
              import React from 'react'
              import IngredientsList from './ingredients-list'
              import Instructions from './instructions'
              import StarRating from './star-rating'
               
              const Recipe = ({name, ingredients, steps, rating, onRate}) =>
                <section id={name.toLowerCase().replace(/\s/g, '-')}>
                  <h2>{name}</h2>
                  <StarRating starsSelected={rating} onRate={onRate}/>
                  <IngredientsList list={ingredients}/>
                  <Instructions title="Cooking Instructions" steps={steps}/>
                </section>
               
              export default Recipe;


            menu.js:
            ExpandedWrap disabled
              import React from 'react';
              import Recipe from './recipe';
              import Summary from './summary';
               
              const Menu = ({recipes=[], onRate=()=>{}}) => {
                return <article>
                  <header>
                    <h1 className="text-white bg-dark">Delicious Recipes</h1>
                  </header>
                  <div className="recipes">
                    {recipes.map((recipe, i) =>
                      <div key={i}>
                        <Recipe {...recipe} onRate={rating => onRate(recipe.id, rating)}/>
                        <Summary ingredientsCount={recipe.ingredients.length}
                          stepsCount={recipe.steps.length}/>
                        <hr/>
                      </div>)}
                  </div>
                </article>
              }
               
              export default Menu;


            app.js:
            ExpandedWrap disabled
              import React, {Component} from 'react';
              import {render} from 'react-dom';
              import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
              import Menu from './components/menu';
              import data from '../db/recipes';
               
              class App extends Component {
                constructor(props) {
                  super(props);
                  this.state = {
                    recipes: props.data.recipes
                  };
                  this.doRate = this.doRate.bind(this);
                }
                doRate(id, rating) {
                  const {recipes} = this.state;
                  this.setState({
                    recipes: recipes.map(recipe => {
                      return (id !== recipe.id)
                        ? recipe
                        : {
                          ...recipe,
                          rating
                        }
                    })
                  });
                }
                render() {
                  const {recipes} = this.state;
                  return (
                    <div className="container">
                      <Menu recipes={recipes} onRate={this.doRate}/>
                    </div>
                  );
                }
              }
               
              render(<App data={data}/>, document.getElementById('app'));


            Прикреплённый файлПрикреплённый файлreact_in_action_v2.zip (6,9 Кбайт, скачиваний: 2)
            Сообщение отредактировано: Cfon -
              А теперь самое интересное!
              Добавляем форму для добавления рецепта, она будет состоять из трех частей: AddRecipeForm, AddIngredients и AddInstructions.
              Здесь продемонстрировано как получить доступ к элементам DOM через this.refs для сбора информации из формы, также еще раз поработаем с состоянием, но локальным, оно необходимо для временного хранения списка инградиентов и инструкций добавляемого рецепта!
              Вот смотрите мой перл что получилось :D

              add-recipe-form.js:
              ExpandedWrap disabled
                import React, {Component} from 'react';
                import IngredientList from './ingredients-list';
                import AddIngredients from './add-ingredients';
                 
                class AddRecipeForm extends Component {
                  static defaultProps = {
                    onNewRecipe() {}
                  }
                 
                  constructor() {
                    super();
                    this.state = {
                      ingredients: []
                    };
                    this.doSubmitRecipe = this.doSubmitRecipe.bind(this);
                    this.doAddIngredient = this.doAddIngredient.bind(this);
                    this.doClearIngredientList = this.doClearIngredientList.bind(this);
                  }
                 
                  doSubmitRecipe(event) {
                    event.preventDefault();
                    const {_recipeName} = this.refs;
                    const {onNewRecipe} = this.props;
                    const {ingredients} = this.state;
                    onNewRecipe(_recipeName.value, ingredients);
                    this.setState({
                      ingredients: []
                    });
                    _recipeName.value = '';
                    _recipeName.focus();
                  }
                 
                  doAddIngredient(ingredient) {
                    const {ingredients} = this.state;
                    this.setState({
                      ingredients: [
                        ...ingredients,
                        ingredient
                      ]
                    });
                  }
                 
                  doClearIngredientList() {
                    this.setState({
                      ingredients: []
                    });
                  }
                 
                  render() {
                    const {ingredients} = this.state;
                    return (
                      <div>
                        <h3>Add Recipe</h3>
                        <form onSubmit={this.doSubmitRecipe}>
                          <input className="form-control" ref="_recipeName" type="text" placeholder="Recipe name" required/>
                          <div className="card mt-2">
                            <div className="card-body">
                              {
                                (ingredients.length !== 0)
                                  ? <IngredientList list={ingredients}/>
                                  : <p>No ingredients</p>
                              }
                              <AddIngredients onNewIngredient={this.doAddIngredient}
                                onClearIngredientList={this.doClearIngredientList}/>
                            </div>
                          </div>
                          {/* TODO: add AddInstructions Element */}
                          <button className="btn btn-primary mt-2">Add Recipe</button>
                        </form>
                      </div>
                    );
                  }
                }
                 
                export default AddRecipeForm;


              add-ingredients.js:
              ExpandedWrap disabled
                import React, {Component} from 'react';
                 
                class AddIngredients extends Component {
                  static defaultProps = {
                    onNewIngredient(ingredient) {},
                    onClearIngredientList() {}
                  }
                 
                  constructor() {
                    super();
                    this.doAddIngredient = this.doAddIngredient.bind(this);
                    this.doClearIngredientList = this.doClearIngredientList.bind(this);
                  }
                 
                  doAddIngredient(event) {
                    event.preventDefault();
                    const {_ingredientName, _amount, _measurement} = this.refs;
                    const {onNewIngredient} = this.props;
                    onNewIngredient({
                      name: _ingredientName.value,
                      amount: _amount.value,
                      measurement: _measurement.value
                    });
                    this.clearInputs();
                  }
                 
                  doClearIngredientList(event) {
                    event.preventDefault();
                    const {onClearIngredientList} = this.props;
                    onClearIngredientList();
                    this.clearInputs();
                  }
                 
                  clearInputs() {
                    const {_ingredientName, _amount, _measurement} = this.refs;
                    _ingredientName.value = '';
                    _amount.value = '';
                    _measurement.value = '';
                    _amount.focus();
                  }
                 
                  render() {
                    return (
                      <div className="ingredient-form">
                        <input className="form-control" ref="_amount" type="text" placeholder="Amount"/>
                        <input className="form-control" ref="_measurement" type="text" placeholder="Measurement"/>
                        <input className="form-control" ref="_ingredientName" type="text" placeholder="Ingredient name"/>
                        <div className="mt-2">
                          <button className="btn btn-secondary mr-2" onClick={this.doAddIngredient}>Add Ingredient</button>
                          <button className="btn btn-secondary mr-2" onClick={this.doClearIngredientList}>Clear Ingredient List</button>
                        </div>
                      </div>
                    );
                  }
                }
                 
                export default AddIngredients;


              app.js (с внесеными изменениями):
              ExpandedWrap disabled
                import React, {Component} from 'react';
                import {render} from 'react-dom';
                import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
                import Menu from './components/menu';
                import data from '../db/recipes';
                import AddRecipeForm from './components/add-recipe-form';
                import {v4} from 'uuid';
                 
                class App extends Component {
                 
                  constructor(props) {
                    super(props);
                    this.state = {
                      recipes: props.data.recipes
                    };
                    this.doRate = this.doRate.bind(this);
                    this.doAddRecipe = this.doAddRecipe.bind(this);
                  }
                 
                  doRate(id, rating) {
                    const {recipes} = this.state;
                    this.setState({
                      recipes: recipes.map(recipe => {
                        return (id !== recipe.id)
                          ? recipe
                          : {
                            ...recipe,
                            rating
                          }
                      })
                    });
                  }
                 
                  doAddRecipe(name, ingredients) {
                    const {recipes} = this.state;
                    this.setState({
                      recipes: [
                        ...recipes,
                        {
                          id: v4(),
                          name,
                          ingredients,
                          steps: [],
                          rating: 0
                        }
                      ]
                    });
                  }
                 
                  render() {
                    const {recipes} = this.state;
                    return (
                      <div className="container">
                        <Menu recipes={recipes} onRate={this.doRate}/>
                        <AddRecipeForm onNewRecipe={this.doAddRecipe}/>
                      </div>
                    );
                  }
                }
                 
                render(<App data={data}/>, document.getElementById('app'));

              Прикреплённый файлПрикреплённый файлreact_in_action_v3.zip (8,58 Кбайт, скачиваний: 2)

              Нет элемента AddInstructions? Предлагаю самостоятельно добавить его, как домашнее задание :D
              Ну а если у вас не получиться, то я позже добавлю :jokingly:

              пс. возможно лучше состояние перенести из AddRecipeForm в AddIngredients? тут надо подумать как лучше :blush:
              Сообщение отредактировано: Cfon -
                Эх, ещё с год назад пытался вразумить на счёт ES6+, донести что прототипы мертвы... А сегодня ты уже на реакте фигачишь :-?

                Куда катится мир.
                user posted image
                  Цитата Serafim @
                  Эх, ещё с год назад пытался вразумить на счёт ES6+, донести что прототипы мертвы... А сегодня ты уже на реакте фигачишь :-?

                  Куда катится мир.

                  ну без прототипов знание JS не будет не полным, поэтому я все правильно изучал последовательно и планомерно, скоро перейду на Angular :D
                  1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                  0 пользователей:


                  Рейтинг@Mail.ru
                  [ Script Execution time: 0,1386 ]   [ 19 queries used ]   [ Generated: 22.06.18, 18:26 GMT ]