На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
Модераторы: ElcnU, ANDLL, fatalist
Страницы: (3) 1 2 [3]  все  ( Перейти к последнему сообщению )  
> Изучаю React, modern style: ES7, Babel, webpack, bootstrap
    Цитата Cfon @
    а это статья о чем? разве она про Реле?

    Да, она про Relay реализацию.
    user posted image
      А почему это костыль то? Написано же best practice :)

      Цитата

      Pagination
      The GraphQL type system allows for some fields to return lists of values, but leaves the pagination of longer lists of values up to the API designer. There are a wide range of possible API designs for pagination, each of which has pros and cons.

      Typically fields that could return long lists accept arguments "first" and "after" to allow for specifying a specific region of a list, where "after" is a unique identifier of each of the values in the list.
      Ultimately designing APIs with feature-rich pagination led to a best practice pattern called "Connections". Some client tools for GraphQL, such as Relay, know about the Connections pattern and can automatically provide automatic support for client-side pagination when a GraphQL API employs this pattern.
      Rabbit don't come easy: https://github.com/Cfon/ :D
        Немного изменил пример и заюзал Relay connection вместо массива :D
        Кроме того переписал код GraphQL-сервера с использованием GraphQL схемы (schema.graphql), для этого заюзал пакеты graphql-import и graphql-relay. Также пришлось немного изменить код app.js и menu.js в связи с использование вместо массива реле-соединения, которое юзает edges и node :D.
        Пагинацию не стал реализовывать, возможно потом, но заюзал один из параметров соединения а именно first для указания количества записей, которых в исходной базе тока две :D
        Почекал на получение 1 или 2 записей все пучком работает! :D

        schema.graphql:
        ExpandedWrap disabled
          type Ingredient {
            name: String
            amount: Float
            measurement: String
          }
           
          type Query {
            # recipes: [Recipe]!
            recipes(
              first: Int,
              after: String,
              last: Int,
              before: String
            ): RecipeConnection
          }
           
          type Recipe {
            id: ID!
            name: String!
            ingredients: [Ingredient]
            steps: [String]
            rating: Int!
          }
           
          # A connection to a list of items.
          type RecipeConnection {
            # A list of edges.
            edges: [RecipeEdge]
            # Information to aid in pagination.
            pageInfo: PageInfo
          }
           
          # An edge in a connection.
          type RecipeEdge {
            # The item at the end of the edge.
            node: Recipe
            # A cursor for use in pagination.
            cursor: String
          }
           
          # Information about pagination in a connection.
          type PageInfo {
            # When paginating forwards, are there more items?
            hasNextPage: Boolean
            # When paginating backwards, are there more items?
            hasPreviousPage: Boolean
            # When paginating backwards, the cursor to continue.
            startCursor: String
            # When paginating forwards, the cursor to continue.
            endCursor: String
          }


        server.js
        ExpandedWrap disabled
          import express from 'express';
          import logger from 'morgan';
          import Bourne from 'bourne';
          import path from 'path';
          import graphqlHTTP from 'express-graphql';
          import {buildSchema} from 'graphql';
          import {importSchema} from 'graphql-import'; //<-- 1
          import {connectionFromArray} from 'graphql-relay'; //<-- 2
           
          const app = express();
           
          app.use(logger('dev'));
          app.use(express.static('public'));
           
          const recipes = new Bourne('db/recipes.json');
           
          // Construct a schema, using GraphQL schema language
          const schemaDef = importSchema(path.resolve('schema.graphql')); //<-- 4
          const schema = buildSchema(schemaDef); //<-- 5
           
          // The root provides a resolver function for each API endpoint
          const root = { //<-- 6
            recipes(connArgs) {
              let conn = {};
              recipes.find((err, results) => {
                conn = connectionFromArray(results, connArgs);
              });
              return conn;
            }
          };
           
          app.use('/graphql', graphqlHTTP({
            schema,
            rootValue: root, //<-- 7
            graphiql: true
          }));
           
          app.listen(3000, () => console.log(`Application running at 'http://localhost:3000'`));


        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 RecipeForm from './components/recipe-form';
          import {QueryRenderer, graphql} from 'react-relay';
          import environment from './environment';
           
          const ITEMS_PER_PAGE = 10;
           
          class App extends Component {
            // state = {
            //   recipes: []
            // }
           
            constructor() {
              super();
              this.doRate = this.doRate.bind(this);
              this.addRecipe = this.addRecipe.bind(this);
            }
           
            doRate(id, rating) {
              // TODO:
            }
           
            addRecipe(name, ingredients, steps) {
              // TODO:
            }
           
            // ТУТ МАГИЯ!
            render() {
              return (
                <QueryRenderer
                  environment={environment}
                  query={graphql`
                    query appQuery($count: Int!) {
                      recipes(first: $count) {
                        edges {
                          node {
                            id
                            name
                            ingredients {
                              name
                              amount
                              measurement
                            }
                            steps
                            rating
                          }
                        }
                      }
                    }
                  `}
                  variables={{
                    count: ITEMS_PER_PAGE
                  }}
                  render={({props}) => {
                    return (
                      <div className="container">
                        <Menu recipes={props ? props.recipes.edges : []} onRate={this.doRate}/>
                        <RecipeForm onNewRecipe={this.addRecipe}/>
                      </div>
                    );
                  }}
                />
              );
            }
          }
           
          render(
            <App/>,
            document.getElementById('app')
          );


        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(({node}, i) => {
                              // ^^^
                    return (
                      <div key={i}>
                        <Recipe {...node} onRate={rating => onRate(node.id, rating)}/>
                        <Summary ingredientsCount={node.ingredients.length}
                          stepsCount={node.steps.length}/>
                        <hr/>
                      </div>
                    );
                  })}
                </div>
              </article>
            );
          }
           
          export default Menu;


        Прикреплённый файлПрикреплённый файлreact_in_action_v4_2.zip (10,81 Кбайт, скачиваний: 14)
        Сообщение отредактировано: Cfon -
        Rabbit don't come easy: https://github.com/Cfon/ :D
          Давайте немного отойдем от Реле и вернемся к варианту программы, где юзалось состояние (state), в котором сохранялись данные (см. пост #11)
          Если вы его внимательно изучали, то вероятно заметили как компоненты передают свои данные головному компоненту App, они передают их через параметры колбэков. Ничего в этом плохого нет, но вот в случае сложной иерархии компонентов, легко потерять нить вызовов этих колбэков, как например в случае вызовы doRate! :wacko:
          Если вкраце то там doRate передается по след цепочке Menu => Recipe => StarRating! Причем сама doRate никакого отношения не имеет ни к Menu ни к Recipe, его цель StartRating, зачем тогда передавать по цепочке его? Мы бы его и не передавали если бы состояние не принадлежало App, но поскольку нам нужно сохранять данные о рейтинге в состоянии App, то поэтому мы вынуждены передавать колбэки по цепочке вниз, с тем чтобы потом вернуть данные в состояние App :blink: :wacko: :D
          Короче к чему я это? А к тому что с переносом данных на сервер и подключением Реле, мы естественным путем пришли к другому решению, которое заключается в том что теперь нам не надо передавать данные по цепочке колбэков, мы можем сразу обратиться к данным через запросы к БД. В нашем случае через Реле + Графкуль :D
          Смотрите изменения в нашей программе (см. комментарии в коде), пока тока без реализации самих методов

          ExpandedWrap disabled
            class App extends Component {
              // выкидываем нафик все это
              //constructor() {
              //  super();
              //  this.doRate = this.doRate.bind(this);
              //  this.addRecipe = this.addRecipe.bind(this);
              //}
             
              //doRate(id, rating) {
              //......
              //}
             
              //addRecipe(name, ingredients, steps) {
              //.......
              //}
             
              // остается тока рендер, кстати обычно я юзю функции вместо классов, если не надо работать с состоянием
              // так что можно заменить класс App на функцию
              render() {
                return (
                  <QueryRenderer
                    environment={environment}
                    query={graphql`
                      query appQuery($count: Int!) {
                        recipes(first: $count) {
                          edges {
                            node {
                              id
                              name
                              ingredients {
                                name
                                amount
                                measurement
                              }
                              steps
                              rating
                            }
                          }
                        }
                      }
                    `}
                    variables={{
                      count: ITEMS_PER_PAGE
                    }}
                    render={({props}) => {
                      return (
                        <div className="container">
                          <Menu recipes={props ? props.recipes.edges : []}    />
                                                                       // ^^^ удалили onRate
                          <RecipeForm    />
                                   // ^^^ удалили onNewRecipe
                        </div>
                      );
                    }}
                  />
                );
              }
            }
             
            const Menu = ({recipes=[]) => {
              return (
                <article>
                  <header>
                    <h1 className="text-white bg-dark">Delicious Recipes</h1>
                  </header>
                  <div className="recipes">
                    {recipes.map(({node}, i) => {
                      return (
                        <div key={i}>
                          <Recipe {...node} />
                                        // ^^^ удалили onRate
                          <Summary ingredientsCount={node.ingredients.length}
                            stepsCount={node.steps.length} />
                          <hr/>
                        </div>
                      );
                    })}
                  </div>
                </article>
              );
            }
             
            const Recipe = ({name, ingredients, steps, rating    }) => {
                                                           // ^^^ удалили OnRate
              // собственно тут все и происходит теперь не надо никуда ничего передавать
              const onRate = rating => {
                // TODO: call relay mutation here
              }
              return (
                <section id={name.toLowerCase().replace(/\s/g, '-')}>
                  <h2>{name}</h2>
                  <IngredientsList list={ingredients} />
                  <Instructions title="Cooking Instructions" steps={steps} />
                  <StarRating starsSelected={rating} onRate={onRate}/>
                </section>
              )
            }

          Тоже относится и к методу addRecipe, теперь он расположиться в компоненте RecipeForm и оттуда будет обращаться к мутациям Реле :victory:
          В итоге наши связи упрощаются. Конечно наше приложение не самое сложное, но даже оно я думаю вызвало головную боль, когда вы изучали код вызовов doRate и addRecipe :wall: особенно doRate :D

          пс. кстати это не заслуга Реле само по себе, это заслуга выноса состояния в отдельный объект,
          существуют много подобных решений, например Redux. С ним мы тоже поработаем когда закончим с Relay! :D
          Сообщение отредактировано: Cfon -
          Rabbit don't come easy: https://github.com/Cfon/ :D
            Очередной релиз :D
            Реализовал мутацию! Пока одну :D
            Мутация меняет рейтинг рецепта, теперь звездочки щелкают :D
            Поскольку теперь состояние находится не в App, удалил из свойств компонентов (не всех) колбэки, те что описал в предыдущем посте.
            По просьбе читателей заменил поля в запросах на реле-фрагменты (см. код Recipe, Ingredient)

            schema.graphql:
            ExpandedWrap disabled
              .........
               
              type Mutation {
                changeRecipeRate(input: ChangeRecipeRateInput!): Recipe
              }
               
              input ChangeRecipeRateInput {
                rating: Int!
                id: ID!
              }


            server.js:
            ExpandedWrap disabled
              ..........
               
              // The root provides a resolver function for each API endpoint
              const root = {
               
                .........
               
                // Mutations
                changeRecipeRate: ({input}) => {
                  let recipe = {};
                  recipes.findOne({
                    id: input.id
                  }, (err, result) => {
                    recipe = {
                      ...result,
                      rating: input.rating
                    };
                    recipes.update({id: recipe.id}, recipe);
                  });
                  return recipe;
                }
              };
               
              ............


            app.js:
            ExpandedWrap disabled
              const ITEMS_PER_PAGE = 10;
               
              const App = () => (
                <QueryRenderer
                  environment={environment}
                  query={graphql`
                    query app_Query($count: Int!) {
                      recipes(first: $count) {
                        edges {
                          node {
                            ...recipe_recipe
                          }
                        }
                      }
                    }
                  `}
                  variables={{
                    count: ITEMS_PER_PAGE
                  }}
                  render={({props}) => {
                    return (
                      <div className="container">
                        <Menu recipes={props ? props.recipes.edges : []} />
                        <RecipeForm />
                      </div>
                    );
                  }}
                />
              );


            recipe.js:
            ExpandedWrap disabled
              const Recipe = ({recipe}) => {
                // ВЫЗЫВАЕМ МУТАЦИЮ!
                const onRate = rating => {
                  changeRecipeRate(environment, rating, recipe.id);
                }
                const {name, ingredients, steps, rating} = recipe;
                return (
                  <section id={recipe.name.toLowerCase().replace(/\s/g, '-')}>
                    <h2>{recipe.name}</h2>
                    <IngredientsList list={ingredients}/>
                    <Instructions title="Cooking Instructions" steps={steps}/>
                    <StarRating starsSelected={rating} onRate={onRate}/>
                    <Summary ingredientsCount={ingredients.length}
                      stepsCount={steps.length}/>
                  </section>
                )
              }
               
              // РЕЛЕ-ФРАГМЕНТ!
              export default createFragmentContainer(
                Recipe,
                graphql`
                  fragment recipe_recipe on Recipe {
                    id
                    name
                    ingredients {
                      ...ingredient_item
                    }
                    steps
                    rating
                  }
                `
              );


            change-recipe-rate.js:
            ExpandedWrap disabled
              import {commitMutation, graphql} from 'react-relay';
               
              export default (environment, rating, id) => {
                const variables = {
                  input: {
                    id,
                    rating,
                  }
                };
                return commitMutation(
                  environment,
                  {
                    mutation: graphql`
                      mutation changeRecipeRate_Mutation($input: ChangeRecipeRateInput!) {
                        changeRecipeRate(input: $input) {
                          rating
                        }
                      }
                    `,
                    variables,
                    onError: err => console.error(err)
                  }
                )
              }


            Что тут интересного? Интересное тут то что когда мы щелкаем по звездочкам рейтинга мы видим их реакцию на клики, т.е. интерфейс сам перерисовывается! Вы спросите как это происходит без React this.setState? Да хз :D
            Знаю то что все происходит в хранилище (Store) реле, но глубоко не капал. Думаю что где там делается вызов this.setState ну или вызов ReactDOM.render.

            пс. на самом деле с фрагментами и мутациями я долго мудохался :wall:
            там не все так ясно, как может показаться, поскольку многое скрывает от нас реле-компилятор. Лично я не сразу понял например как формировать имя фрагмента, я долго менял его имя и ловил ошибки то реле-компилятора, то рантайма.
            Также с мутациями, как реле обновляет интерфейс мне не ясно, предстоит еще разобраться с updater и optimisticUpdater для кастомной настройки обновления хранилища.

            Прикреплённый файлПрикреплённый файлreact_in_action_v4_4.zip (11,55 Кбайт, скачиваний: 13)
            Сообщение отредактировано: Cfon -
            Rabbit don't come easy: https://github.com/Cfon/ :D
              Все разобрался с updater и optimisticUpdater, первая вызывается когда приходит ответ с сервера, вторая - до прихода ответа :D
              Я все думал от чего зависит автоматическое обновление реакт-компонентов, оказывается все просто, ну или почти просто :D
              дело в том что чтобы Реле вызвало автоматический апдейт надо чтобы мутационый запрос возвращал глобальный ID, вместе с данными, в моем случае как раз я и возвращал его в объекте Recipe при мутации changeRecipeRate. Ну а поскольку есть глобал ID, то Реле-хранилище имеет возможность обнаружить место куда вносить локальные изменения!
              Я решил проверить свою гениальную идею! :blush: :lool:
              Для этого немного изменил код сервера и схемы, возвращая не весь объект Recipe, а часть вернее тока поле rating, главное не возвращать глобал АЙДИ! :whistle:

              server.js
              ExpandedWrap disabled
                ........
                 
                const root = {
                  .......
                 
                  // Mutations
                  changeRecipeRate: ({input}) => {
                    let recipe = {};
                    recipes.findOne({
                      id: input.id
                    }, (err, result) => {
                      recipe = {
                        ...result,
                        rating: input.rating
                      };
                      recipes.update({id: recipe.id}, recipe);
                    });
                 
                    // 1
                    // return recipe;
                    return {
                      // NO ID!  
                      rating: recipe.rating
                    };
                  }
                };


              schema.graphql:
              ExpandedWrap disabled
                type Mutation {
                  # 2
                  changeRecipeRate(input: ChangeRecipeRateInput!): ChangeRecipeRatePayload
                  # changeRecipeRate(input: ChangeRecipeRateInput!): Recipe
                }
                 
                # 3
                type ChangeRecipeRatePayload {
                  rating: Int
                  # NO ID!
                }


              Скомпилирил запустил и вуаля нифига уже зведочки не кликаются :D
              К сведению, что такое глобал ID что это за зверь. Глобал ID это уникальный ID который дается объекту при его создании, например в нашем случае он есть у объекта Recipe (см. schema.graphql)

              Теперь к тому как нам в этом случае помогут updater и optimicticUpdater. Надо юзать методы Реле-хранилища, чтобы получить доступ к соотвествующим объектам хранилища, не буду ща их перечислять и что они делают, скажу лишь то что через store.getRootField('changeRecipeRate') получаем возращеный объект мутационого запроса, а через store.get(id) получаем доступ к существующему объекту в Реле-хранилище по его глобал ID, ну и меняем там нужные значения. Короче в коде все написано!

              change-recipe-rate.js:
              ExpandedWrap disabled
                import {commitMutation, graphql} from 'react-relay';
                 
                export default (environment, rating, id) => {
                  const variables = {
                    input: {
                      rating,
                      id
                    }
                  };
                  return commitMutation(
                    environment,
                    {
                      mutation: graphql`
                        mutation changeRecipeRate_Mutation($input: ChangeRecipeRateInput!) {
                          changeRecipeRate(input: $input) {
                            rating
                          }
                        }
                      `,
                      variables,
                      // 1
                      optimisticUpdater: store => {
                        const recipe = store.get(id);
                        recipe.setValue(rating, 'rating');
                      },
                      // 2
                      updater: store => {
                        const root = store.getRootField('changeRecipeRate');
                        const rating = root.getValue('rating');
                        const recipe = store.get(id);
                        recipe.setValue(rating, 'rating');
                      },
                      onError: err => console.error(err)
                    }
                  )
                }


              Как вы уже догадались зведочки рейтинга закликали :D

              пс. поскольку кликать по звездам можно много и быстро, то можно легко посадить сервер! :jokingly:
              поэтому надо предусмотреть защиту от повторных запросов.

              Прикреплённый файлПрикреплённый файлreact_in_action_v4_4_1.zip (11,9 Кбайт, скачиваний: 11)
              Сообщение отредактировано: Cfon -
              Rabbit don't come easy: https://github.com/Cfon/ :D
                Добавил мутацию addRecipe! Пришлось попотеть надеюсь оцените :D

                Кроме того изменил код GraphQL-сервера, поскольку допустил ряд ошибок и не учел асинхроность операций работы с БД.
                Код работал, потому что в моем случае все операции выполнялись синхронно и поэтому не было ошибок рантайма.
                В следущем релизе я учел асинхронность, заюзав промисификацию к БД методам и упорядочил их вызовы через async + await! :D
                Для того что работали async await надо подключить пакет babel-polyfill через webpack.config.js.

                server.js:
                ExpandedWrap disabled
                  import express from 'express';
                  import logger from 'morgan';
                  import Bourne from 'bourne';
                  import path from 'path';
                  import graphqlHTTP from 'express-graphql';
                  import {buildSchema} from 'graphql';
                  import {importSchema} from 'graphql-import';
                  import {connectionFromArray} from 'graphql-relay';
                  import {promisify} from 'util';
                   
                  const app = express();
                  const recipes = new Bourne('db/recipes.json');
                   
                  app.use(logger('dev'));
                  app.use(express.static('public'));
                   
                  // Construct a schema, using GraphQL schema language
                  const schemaDef = importSchema(path.resolve('server', 'schema.graphql'));
                  const schema = buildSchema(schemaDef);
                   
                  // promisify db methods
                  const findRecipes = promisify(recipes.find.bind(recipes));
                  const findOneRecipe = promisify(recipes.findOne.bind(recipes));
                  const updateRecipe = promisify(recipes.update.bind(recipes));
                  const insertRecipe = promisify(recipes.insert.bind(recipes));
                   
                  // The root provides a resolver function for each API endpoint
                  const root = {
                    // Queries
                    recipes: async (args) => {
                      const conn = await findRecipes()
                        .then(recipes => connectionFromArray(recipes, args));
                      return conn;
                    },
                    // Mutations
                    changeRecipeRate: async ({input}) => {
                      const id = parseInt(input.id, 10);
                      let recipe = await findOneRecipe({id});
                      recipe = await updateRecipe({id}, {
                        ...recipe,
                        rating: input.rating
                      }).then(recipes => recipes[0]);
                      return {recipe};
                    },
                    addRecipe: async ({input}) => {
                      const {name, rating} = input;
                      const ingredients = [...input.ingredients];
                      const instructions = [...input.instructions];
                      const recipe = await insertRecipe({
                        name,
                        ingredients,
                        instructions,
                        rating
                      });
                      return {recipe};
                    }
                  };
                   
                  app.use('/graphql', graphqlHTTP({
                    schema,
                    rootValue: root,
                    graphiql: true
                  }));
                   
                  app.listen(3000, () => console.log(`Application running at 'http://localhost:3000'`));


                остальные изменения см в архиве
                schema.graphql
                webpack.config.js
                server.js
                add-recipe.js
                recipe-form.js
                app.js

                С добавлением нового рецепта прошлось тоже мудохацца! :wall:
                То одно то другое то вапще жрать захотелось :lool:

                Объясняю по шагам, вы в надежных руках, ваш моск не пострадает :D
                1. Пишем код root API для сервера, метод addRecipe
                2. Добавляем в GraphQL схему новую мутацию addRecipe и сопутствующие типы (см schema.graphql)
                тестим мутацию addRecipe на localhost:3000/graphql, если запрос добавляет новую запись то продолжаем
                3. Добавляем директиву @connection(key: "app_Query_recipes") в app.js
                (имя ключ произвольное) это необходимо если мы юзаем коннекшен
                4. Пишем код мутации addRecipe (см add-recipe.js) согласно ее определению в схеме (см. schema.graphql)
                поскольку в отличие от мутации changeRecipeRate тут возвращется новый объект, Реле не сможет автоматом добавить его в хранилище, поэтому надо определить updater, который передается в commitMutation.
                ВСЕ!
                да еще надо изменить код submitRecipe в RecipeForm поскольку теперь мы не передаем колбэк через свойтсво onNewRecipe, а напрямую вызываем мутацию addRecipe предварительно импортировав ее.
                УФФ! Теперь ВСЕ! :D

                Прикреплённый файлПрикреплённый файлreact_in_action_v4_5.zip (12,54 Кбайт, скачиваний: 16)

                пс. заметьте размер архива увеличивается постепенно :popcorn:
                Сообщение отредактировано: Cfon -
                Rabbit don't come easy: https://github.com/Cfon/ :D
                  Продолжаю расширять функционал нашего учебного примера и добавил мутацию removeRecipe! :D

                  Теперь можно добавлять и удалять записи рецептов, правда не стал писать предупреждение перед удалением, все это можно потом добавить, а наша задача разобраться в сути Реле не так ли?

                  Также заюзал библу reactstrap, хотя можно обойтись и чисто bootstrap, но так для разминки заменил на reactstrap, вроде как чуть меньше кодить и запоминать атрибутов bootstrap. Немного изменил оформление приложения, как по мне оно получилось довольно презентабельно, хотя я еще тот спец по офомлению :jokingly:

                  Правда при замене пришлось помудохаться (ну как же без этого >:() с атрибутом ref в <Input>, точнее его отсутствием :D, как оказалось его надо заменить на innerRef, но это еще не все, прежняя форма записи <Input innerRef="_nameInput" /> не сработала и я никак не мог понять как запомнить ссылку на DOM элемент <input> и потом обращаться к ней через this.refs как раньше. Как оказалось надо юзать <Input innerRef={input=>this._nameInput=input}/> и потом обращаться напрямую через this._nameInput, хотя с <input> можно юзать оба способа. Не стал копаться в причинах этого поведения, хотя краем глаза глянул в исходники reactstrap его Input.js и как я понял он также допускает обе формы записи, т.е. в свойство innerRef можно передавать строку или функцию или объект :scratch:

                  см в архиве:
                  schema.graphql
                  server.js
                  remove-recipe.js (реализация мутации на стороне клиента)
                  recipe.js

                  Прикреплённый файлПрикреплённый файлreact_in_action_v4_6.zip (13,41 Кбайт, скачиваний: 13)
                  Сообщение отредактировано: Cfon -
                  Rabbit don't come easy: https://github.com/Cfon/ :D
                    Тебе бы посты на хабр или стримчки пилить :whistle:
                    user posted image
                      Заждались меня робяты :D
                      Или нет? да пофик :D

                      о чем я хотел сегодня поговорить...

                      ранее я запостил пост #10 про componentWillReceiveProps, так вот он уже устарел и вместо него в начиная с 16.3 версии юзается getDerivedStateFromProps, он вроде как более безопаснее, чем componentWillReceiveProps, в чем? да хз я сам запуталси :D
                      вот ссылка сами читайте getDerivedStateFromProps
                      и кроме того их вапще не рекомендуют юзать! типо есть альтернативы им см тут You Probably Dont Need Derived State

                      Далее разобрался в непонятках с refs см Refs and the DOM
                      Как оказалось refs тоже устарел и больше не юзается, вместо него надо юзать React.createRef() или колбэк как в моем примере из поста #38
                      Кроме того в статье описывается новая фича React.forwardRef() пока на практике мне не пригодилась, но смысл понял.
                      Если вкраце, то это способ выставления ссылки на внутрений DOM элемент дочернего компонента для использования его из родительского компонета :blink: :D
                      Вот цитирую :whistle:
                      Цитата
                      Ref forwarding is a technique for automatically passing a ref through a component to one of its children. This is typically not necessary for most components in the application. However, it can be useful for some kinds of components, especially in reusable component libraries. The most common scenarios are described below.

                      Перевожу :D
                      Цитата
                      Ref forwarding (пересылка ссылки :jokingly:) - это способ автоматической передачи ref через компонент к одному из его дочерних элементов. Обычно это не требуется для большинства компонентов приложения. Однако это может быть полезно для некоторых видов компонентов, особенно в библиотеках компонентов многократного использования. Наиболее распространенные сценарии описаны ниже.

                      Теперь понятно почему мне оно еще не пригодилось. Я не пишу библиотеки :D
                      Что я пишу? Чат-боты для форум на исходниках :lool:

                      Добавлено
                      Цитата Serafim @
                      Тебе бы посты на хабр или стримчки пилить :whistle:

                      тут пока буду упражняться :D
                      Сообщение отредактировано: Cfon -
                      Rabbit don't come easy: https://github.com/Cfon/ :D
                        Цитата Cfon @
                        Я не пишу библиотеки

                        А я вот пишу) Написал компиль для SDL :whistle:

                        У меня у енамов можно значения проставлять, а у тебя нет :tong:


                        Кстати, а во что в JS собирается вот такой код?
                        ExpandedWrap disabled
                          input A {
                              a: B! = {
                                  b: {
                                      c: {
                                          a: {}
                                      }
                                  }
                              }
                          }
                           
                          input B { b: C! }
                          input C { c: A! }


                        Я просто довольно много попарился в компиляторе с детектом рекурсивных отношений во всяких инпутабл типах:
                        Скрытый текст
                        user posted image
                        Сообщение отредактировано: Serafim -
                        user posted image
                          Цитата Serafim @
                          Кстати, а во что в JS собирается вот такой код?
                          ExpandedWrap disabled
                            input A {
                                a: B! = {
                                    b: {
                                        c: {
                                            a: {}
                                        }
                                    }
                                }
                            }
                             
                            input B { b: C! }
                            input C { c: A! }


                          у меня не собирается, ошибка компиляции в function isType(type) из node_modules/graphql/type/definition.js:
                          ExpandedWrap disabled
                            RangeError: Maximum call stack size exceeded
                          Rabbit don't come easy: https://github.com/Cfon/ :D
                            Цитата Cfon @
                            RangeError: Maximum call stack size exceeded

                            :crazy:
                            user posted image
                            1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                            0 пользователей:
                            Страницы: (3) 1 2 [3]  все


                            Рейтинг@Mail.ru
                            [ Script Execution time: 0,2618 ]   [ 22 queries used ]   [ Generated: 15.11.18, 21:16 GMT ]