Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.145.15.1] |
|
Страницы: (3) [1] 2 3 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Изучаю React, а вы?
Вот набросал примерчик может кому пригодится для изучения в нем я максимльно следовал современому стилю программирования на React Сам пока до конца не изучил все тонкости React, но думаю заброшу Backbone и перейду на React /public/index.html: <!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: 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: 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: 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: 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: 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 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: { "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: 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 Кбайт, скачиваний: 71) пс. чтобы все заработало надо выполнить команду npm install, далее npm run build, далее npm start, далее в броузере Chrome набрать localhost:3000 ВСЕ! |
Сообщ.
#2
,
|
|
|
Цитата Cfon думаю заброшу Backbone что, так много на нём уже сделал, что аж бросать приходится? |
Сообщ.
#3
,
|
|
|
Цитата K313 @ что, так много на нём уже сделал, что аж бросать приходится? да много всего уже написано на Backbone ~10k строк гавнокода |
Сообщ.
#4
,
|
|
|
хотя Backbone ешо на плаву поскольку можно юзать его в связке с React
надо будет переделать Simple Blog с React+Backbone для практики |
Сообщ.
#5
,
|
|
|
Итак продолжаю изучать React!
добавил два компонента Summary и StarRating. в первом показано как в React работать с типами и значениями по умолчанию, второй вводит интерактивность в приложение, в связи в этим было введено состояние (state) у компонента App! смотрите и учитесь, ну или критикуйте summary.js: 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: 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: 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: 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: 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 Кбайт, скачиваний: 141) |
Сообщ.
#6
,
|
|
|
А теперь самое интересное!
Добавляем форму для добавления рецепта, она будет состоять из трех частей: AddRecipeForm, AddIngredients и AddInstructions. Здесь продемонстрировано как получить доступ к элементам DOM через this.refs для сбора информации из формы, также еще раз поработаем с состоянием, но локальным, оно необходимо для временного хранения списка инградиентов и инструкций добавляемого рецепта! Вот смотрите мой перл что получилось add-recipe-form.js: 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: 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 (с внесеными изменениями): 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 Кбайт, скачиваний: 147) Нет элемента AddInstructions? Предлагаю самостоятельно добавить его, как домашнее задание Ну а если у вас не получиться, то я позже добавлю пс. возможно лучше состояние перенести из AddRecipeForm в AddIngredients? тут надо подумать как лучше |
Сообщ.
#7
,
|
|
|
Эх, ещё с год назад пытался вразумить на счёт ES6+, донести что прототипы мертвы... А сегодня ты уже на реакте фигачишь
Куда катится мир. |
Сообщ.
#8
,
|
|
|
Цитата Serafim @ Эх, ещё с год назад пытался вразумить на счёт ES6+, донести что прототипы мертвы... А сегодня ты уже на реакте фигачишь Куда катится мир. ну без прототипов знание JS не будет не полным, поэтому я все правильно изучал последовательно и планомерно, скоро перейду на Angular |
Сообщ.
#9
,
|
|
|
Цитата Cfon @ Нет элемента AddInstructions? Предлагаю самостоятельно добавить его, как домашнее задание Ответ на домашнее задание: Вместо класса юзал функцию, в реакте такой стиль написания кода поощряется и если вы будете так писать вас возмут в Facebook собственно instruction-form.js import React from 'react'; const InstructionForm = ({onNewInstruction=f=>f, onClear=f=>f}) => { let _step; const add = (e) => { e.preventDefault(); onNewInstruction(_step.value); clearInputs(); } const clear = (e) => { e.preventDefault(); onClear(); } const clearInputs = () => { _step.value = ''; } return ( <div className="instruction-form"> <input className="form-control" ref={i => _step = i} type="text" placeholder="Step"/> <div className="mt-2"> <button className="btn btn-secondary mr-2" onClick={add}>Add Instruction</button> <button className="btn btn-secondary mr-2" onClick={clear}>Clear Instruction List</button> </div> </div> ); }; export default InstructionForm; изменения в recipe-form.js: import React, {Component} from 'react'; import IngredientList from './ingredients-list'; import IngredientForm from './ingredient-form'; import Instructions from './instructions'; import InstructionForm from './instruction-form'; class RecipeForm extends Component { static defaultProps = { onNewRecipe() {} } constructor() { super(); this.state = { ingredients: [], instructions: [] //<-- }; this.submitRecipe = this.submitRecipe.bind(this); this.addIngredient = this.addIngredient.bind(this); this.clearIngredientList = this.clearIngredientList.bind(this); this.addInstruction = this.addInstruction.bind(this); //<-- this.clearInstructions = this.clearInstructions.bind(this); //<-- } submitRecipe(event) { event.preventDefault(); const {_recipeName} = this.refs; const {onNewRecipe} = this.props; const {ingredients, instructions} = this.state; // ^^^ onNewRecipe(_recipeName.value, ingredients, instructions); // ^^^ this.clearIngredientList(); this.clearInstructions(); //<-- _recipeName.value = ''; _recipeName.focus(); } addIngredient(ingredient) { const {ingredients} = this.state; this.setState({ ingredients: [ ...ingredients, ingredient ] }); } clearIngredientList() { this.setState({ ingredients: [] }); } addInstruction(step) { //<-- const {instructions} = this.state; this.setState({ instructions: [ ...instructions, step ] }); } clearInstructions() { //<-- this.setState({ instructions: [] }); } render() { const {ingredients, instructions} = this.state; return ( <div> <h3>Add Recipe</h3> <form onSubmit={this.submitRecipe}> <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> } <IngredientForm onNewIngredient={this.addIngredient} onClear={this.clearIngredientList}/> </div> </div> {/* AND HERE */} <div className="card mt-2"> <div className="card-body"> { (instructions.length !== 0) ? <Instructions steps={instructions}/> : <p>No instructions</p> } <InstructionForm onNewInstruction={this.addInstruction} onClear={this.clearInstructions}/> </div> </div> <button className="btn btn-primary mt-2">Add Recipe</button> </form> </div> ); } } export default RecipeForm; изменения в app.js: 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 RecipeForm from './components/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.addRecipe = this.addRecipe.bind(this); } doRate(id, rating) { const {recipes} = this.state; this.setState({ recipes: recipes.map(recipe => { return (id !== recipe.id) ? recipe : { ...recipe, rating } }) }); } addRecipe(name, ingredients, steps) { // ^^^ const {recipes} = this.state; const recipe = { id: v4(), name, ingredients, steps, //<-- rating: 0 }; this.setState({ recipes: [ ...recipes, recipe ] }); } render() { const {recipes} = this.state; return ( <div className="container"> <Menu recipes={recipes} onRate={this.doRate}/> <RecipeForm onNewRecipe={this.addRecipe}/> </div> ); } } render(<App data={data}/>, document.getElementById('app')); Прикреплённый файлreact_in_action_v3_1.zip (9,43 Кбайт, скачиваний: 143) Пока все! Продолжение следует... |
Сообщ.
#10
,
|
|
|
Так продолжаю изучать React и сегодня я разбираюсь c жизненным циклом обновления компоненты (updating component lifecycle)
а именно с методом componentWillReceiveProps, который как мне показалось является самым сложным в понимании его использования. В качестве примера решил взять уже избитый мною класс Person, но переложеный на React Короче вот пример: person.js class Person extends Component { constructor() { super(); this.state = { isYoung: false }; } // А ВОТ И ОН! componentWillReceiveProps(nextProps) { this.setState({ isYoung: nextProps.age < 20 }); } render() { const {name, isSelected, onClick} = this.props; const {isYoung} = this.state; return ( <li className={isSelected ? (isYoung ? 'young-selected' : 'selected') : ''} onClick={onClick}> {name} </li> ); } } persons.js const Persons = ({persons=[], onSelect=f=>f}) => ( <ul className="persons-list"> {persons.map((person, i) => <Person key={i} {...person} onClick={() => onSelect(i)}/> )} </ul> ); person-form.js const PersonForm = ({onSubmit=f=>f}) => { let nameInput, ageInput; const submit = e => { e.preventDefault(); onSubmit(nameInput.value, ageInput.value); clearInputs(); }; const clearInputs = () => { nameInput.value = ''; ageInput.value = ''; nameInput.focus(); }; return ( <form onSubmit={submit}> <input type="text" ref={i => nameInput = i} placeholder="Name"/> <input type="text" ref={i => ageInput = i} placeholder="Age"/> <button>Submit</button> </form> ); }; app.js class App extends Component { constructor() { super(); this.state = { persons: [] }; this.addPerson = this.addPerson.bind(this); this.selectPerson = this.selectPerson.bind(this); } addPerson(name, age) { const {persons} = this.state; const person = { name, age, isSelected: false }; this.setState({ persons: [ ...persons, person ] }); } selectPerson(index) { const {persons} = this.state; this.setState({ persons: persons.map((person, i) => { return (index !== i) ? person : { ...person, isSelected: !person.isSelected } }) }); } render() { const {persons} = this.state; return ( <div> <PersonForm onSubmit={this.addPerson}/> <Persons persons={persons} onSelect={this.selectPerson}/> </div> ); } } render( <App />, document.getElementById('app') ); Объяснение этого кода я отложу не потом, а пока просто изучите его, возможно что вы и сами поймете наконец как юзать componentWillReceiveProps Если вкраце то его юзат как правило для обновления состояния дочернего компонента при изменении его свойств родительским компонентом |
Сообщ.
#11
,
|
|
|
Меня многие спрашивают а как перенести данные на сервер?!
Отвечаю например постаринке юзать AJAX и REST API В качестве обертки AJAX можно юзать пакеты isomorphic-fetch или axios. Или тот же jQuery утсанавливаем их командой yarn add isomorphic-fetch или yarn add axios Хотя в последней версии хрома isomorphic-fetch уже поддержвается так что можно не устанавливать. Также в примере показано использование метода жизненного цикла componentWillMount Вот код изменения в коде сервера и клиента, в качестве БД взял NoSQL Bourne, при желании можно заменить на MongoDB: server.js import express from 'express'; import logger from 'morgan'; import Bourne from 'bourne'; const app = express(); const recipes = new Bourne('db/recipes.json'); app.use(logger('dev')); app.use(express.static('public')); app.get('/', (req, res) => { res.sendFile('index.html'); }); // REST API app.get('/recipes.json', (req, res) => { recipes.find((err, results) => { res.json(results); }); }); app.listen(3000, () => console.log(`Application running at 'http://localhost:3000'`)); app.js 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 {v4} from 'uuid'; import fetch from 'isomorphic-fetch'; // import axios from 'axios'; class App extends Component { constructor(props) { super(props); this.state = { recipes: [] }; this.doRate = this.doRate.bind(this); this.addRecipe = this.addRecipe.bind(this); } // Получаем данные с сервера componentWillMount() { fetch(`/recipes.json`) .then(res => res.json()) .then(recipes => this.setState({recipes})) .catch(err => console.error(err)); } doRate(id, rating) { const {recipes} = this.state; this.setState({ recipes: recipes.map(recipe => { return (id !== recipe.id) ? recipe : { ...recipe, rating } }) }); } addRecipe(name, ingredients, steps) { const {recipes} = this.state; const recipe = { id: v4(), name, ingredients, steps, rating: 0 }; this.setState({ recipes: [ ...recipes, recipe ] }); } render() { const {recipes} = this.state; return ( <div className="container"> <Menu recipes={recipes} onRate={this.doRate}/> <RecipeForm onNewRecipe={this.addRecipe}/> </div> ); } } render(<App />, document.getElementById('app')); Прикреплённый файлreact_in_action_v3_2.zip (9,63 Кбайт, скачиваний: 144) |
Сообщ.
#12
,
|
|
|
Цитата Cfon @ Отвечаю например постаринке юзать AJAX и REST API REST API REST IN PEACE |
Сообщ.
#13
,
|
|
|
Цитата Serafim @ REST API REST IN PEACE Hail to the GraphQL? |
Сообщ.
#14
,
|
|
|
Цитата Астарот @ Hail to the GraphQL? О, а вот ты шаришь в трендах этого грешного мира, а притворялся джавистом. |
Сообщ.
#15
,
|
|
|
Цитата Serafim @ О, а вот ты шаришь в трендах этого грешного мира, а притворялся джавистом. Так те же яйца, только в профиль |