(hello-react)React: Section 5: Styling React Components & Elements

第 5 節練習:學習加上 Styles。

1. Setting Styles Dynamically

App.js

修改

const style = {

backgroundColor: 'green',

color: 'white',

font: 'inherit',

border: '1px solid blue',

padding: '8px',

cursor: 'pointer'

};

以及增加 77 行的

style.backgroundColor = 'red';

// import logo from './logo.svg';
import './App.css';
// import { render } from 'react-dom';
import { Component } from 'react';
import Person from './Person/Person';
import person from './Person/Person';

//function App() {
class App extends Component{
  state = {
    persons: [
      { id:'asfa1', name:'Max', age: 28 },
      { id:'vasfa1', name:'Manu', age: 29 },
      { id:'asdf1', name:'Stephanie', age: 26 }
    ],
    otherState: 'Some other value',
    showPersons: false
  } 

  nameChangedHandler = (event, id) => {
    const personIndex =this.state.persons.findIndex(p => {
      return p.id === id;
    });

    const person = {
      ...this.state.persons[personIndex]
    };

    //const person = Object.assign({}, this.state.persons[personIndex])

    person.name = event.target.value;

    const persons = [...this.state.persons];
    persons[personIndex] = person;

    this.setState({persons: persons});
  }

  deletePersonHandler = (personIndex) => {
    //const persons = this.state.persons.splice();
    const persons = [...this.state.persons];
    persons.splice(personIndex, 1);
    this.setState({persons: persons})
  }

  togglePersonHandler = () => {
    const doesShow = this.state.showPersons;
    this.setState({showPersons: !doesShow});
  }

  render() {
    const style = {
      backgroundColor: 'green',
      color: 'white',
      font: 'inherit',
      border: '1px solid blue',
      padding: '8px',
      cursor: 'pointer'
    };

    let persons = null;

    if (this.state.showPersons === true) {
      persons = (
        <div>
          {this.state.persons.map((person, index) => {
            return <Person 
              click={() => this.deletePersonHandler(index)}
              name={person.name} 
              age={person.age}
              key={person.id}
              changed={(event) => this.nameChangedHandler(event, person.id)} />
          })}
        </div>
      );

      style.backgroundColor = 'red';
    }

    return (
      <div className="App">
        <h1>Hi, I'm a React App, Wendy</h1>
        <p>This is really working!</p>
        <button style={style} onClick={this.togglePersonHandler}>Taggle Persons</button>
        {persons}
      </div>
    );
    //return React.createElement('div', {classname: 'App'}, React.createElement('h1', null, 'Does this work now?'));
  }
}

export default App;

呈現結果:按鈕未點選時是綠色,點選後變成紅色。

2. Setting Class Names Dynamically

App.js

// import logo from './logo.svg';
import './App.css';
// import { render } from 'react-dom';
import { Component } from 'react';
import Person from './Person/Person';
//import person from './Person/Person';
//import classes from '*.module.css';

//function App() {
class App extends Component{
  state = {
    persons: [
      { id:'asfa1', name:'Max', age: 28 },
      { id:'vasfa1', name:'Manu', age: 29 },
      { id:'asdf1', name:'Stephanie', age: 26 }
    ],
    otherState: 'Some other value',
    showPersons: false
  } 

  nameChangedHandler = (event, id) => {
    const personIndex =this.state.persons.findIndex(p => {
      return p.id === id;
    });

    const person = {
      ...this.state.persons[personIndex]
    };

    //const person = Object.assign({}, this.state.persons[personIndex])

    person.name = event.target.value;

    const persons = [...this.state.persons];
    persons[personIndex] = person;

    this.setState({persons: persons});
  }

  deletePersonHandler = (personIndex) => {
    //const persons = this.state.persons.splice();
    const persons = [...this.state.persons];
    persons.splice(personIndex, 1);
    this.setState({persons: persons})
  }

  togglePersonHandler = () => {
    const doesShow = this.state.showPersons;
    this.setState({showPersons: !doesShow});
  }

  render() {
    const style = {
      backgroundColor: 'green',
      color: 'white',
      font: 'inherit',
      border: '1px solid blue',
      padding: '8px',
      cursor: 'pointer'
    };

    let persons = null;

    if (this.state.showPersons === true) {
      persons = (
        <div>
          {this.state.persons.map((person, index) => {
            return <Person 
              click={() => this.deletePersonHandler(index)}
              name={person.name} 
              age={person.age}
              key={person.id}
              changed={(event) => this.nameChangedHandler(event, person.id)} />
          })}
        </div>
      );

      style.backgroundColor = 'red';
    }

    //let classes = ['red', 'bold'].join(' ');
    const classes = [];
    if (this.state.persons.length <= 2) {
      classes.push('red'); //classes = ['red']
    }
    if (this.state.persons.length <= 1) {
      classes.push('bold'); //classes = ['red', 'bold']
    }

    return (
      <div className="App">
        <h1>Hi, I'm a React App, Wendy</h1>
        <p className={classes.join(' ')}>This is really working!</p>
        <button style={style} onClick={this.togglePersonHandler}>Taggle Persons</button>
        {persons}
      </div>
    );
    //return React.createElement('div', {classname: 'App'}, React.createElement('h1', null, 'Does this work now?'));
  }
}

export default App;

App.css

.App {
  text-align: center;
}

.red {
  color: red;
}

.bold {
  font-weight: bold;
}

button{
  
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

呈現結果:當卡片長度>=2時,This is really working!會變成紅色,當卡片長度>=1時,This is really working!會變成紅色+粗體。

3. Adding and Using Radium

安裝 radium:npm install --save radium

過程中遇到無法執行程式...

有可能一開始有東西沒有裝好/版本問題:

先移除一些東西:rm -rf node_modules package-lock.json

再重新安裝:npm install

執行後恢復正常。

App.js

// import logo from './logo.svg';
import React, { Component } from 'react';
import './App.css';
import Radium from 'radium';
// import { render } from 'react-dom';
import Person from './Person/Person';
//import person from './Person/Person';
//import classes from '*.module.css';

//function App() {
class App extends Component{
  state = {
    persons: [
      { id:'asfa1', name:'Max', age: 28 },
      { id:'vasfa1', name:'Manu', age: 29 },
      { id:'asdf1', name:'Stephanie', age: 26 }
    ],
    otherState: 'Some other value',
    showPersons: false
  } 

  nameChangedHandler = (event, id) => {
    const personIndex =this.state.persons.findIndex(p => {
      return p.id === id;
    });

    const person = {
      ...this.state.persons[personIndex]
    };

    //const person = Object.assign({}, this.state.persons[personIndex])

    person.name = event.target.value;

    const persons = [...this.state.persons];
    persons[personIndex] = person;

    this.setState({persons: persons});
  }

  deletePersonHandler = (personIndex) => {
    //const persons = this.state.persons.splice();
    const persons = [...this.state.persons];
    persons.splice(personIndex, 1);
    this.setState({persons: persons})
  }

  togglePersonHandler = () => {
    const doesShow = this.state.showPersons;
    this.setState({showPersons: !doesShow});
  }

  render() {
    const style = {
      backgroundColor: 'green',
      color: 'white',
      font: 'inherit',
      border: '1px solid blue',
      padding: '8px',
      cursor: 'pointer',
      ':hover': {
        backgroundColor: 'lightgreen',
        color: 'black'
      }
    };

    let persons = null;

    if (this.state.showPersons === true) {
      persons = (
        <div>
          {this.state.persons.map((person, index) => {
            return <Person 
              click={() => this.deletePersonHandler(index)}
              name={person.name} 
              age={person.age}
              key={person.id}
              changed={(event) => this.nameChangedHandler(event, person.id)} />
          })}
        </div>
      );

      style.backgroundColor = 'red';
      style[':hover'] = {
        backgroundColor: 'salmon',
        color: 'black'
      }
    }

    //let classes = ['red', 'bold'].join(' ');
    const classes = [];
    if (this.state.persons.length <= 2) {
      classes.push('red'); //classes = ['red']
    }
    if (this.state.persons.length <= 1) {
      classes.push('bold'); //classes = ['red', 'bold']
    }

    return (
      <div className="App">
        <h1>Hi, I'm a React App, Wendy</h1>
        <p className={classes.join(' ')}>This is really working!</p>
        <button style={style} onClick={this.togglePersonHandler}>Taggle Persons</button>
        {persons}
      </div>
    );
    //return React.createElement('div', {classname: 'App'}, React.createElement('h1', null, 'Does this work now?'));
  }
}

export default Radium(App);

Person.js

import React from 'react';
import Radium from 'radium';
import './Person.css';

const person = (props) => {
    return (
        <div className="Person">
            <p onClick={props.click}>I'm {props.name} and I am {props.age} years old.</p>
            <p>{props.children}</p>
            <input type="text" onChange={props.changed} value={props.name}/>
        </div>
    )
}

export default Radium(person);

呈現結果:滑鼠移到按鈕上可以改變顏色(截圖是移到按鈕上時所截取的)。

4. Using Radium for Media Queries

App.js

// import logo from './logo.svg';
import React, { Component } from 'react';
import './App.css';
import Radium, { StyleRoot } from 'radium';
// import { render } from 'react-dom';
import Person from './Person/Person';
//import person from './Person/Person';
//import classes from '*.module.css';

//function App() {
class App extends Component{
  state = {
    persons: [
      { id:'asfa1', name:'Max', age: 28 },
      { id:'vasfa1', name:'Manu', age: 29 },
      { id:'asdf1', name:'Stephanie', age: 26 }
    ],
    otherState: 'Some other value',
    showPersons: false
  } 

  nameChangedHandler = (event, id) => {
    const personIndex =this.state.persons.findIndex(p => {
      return p.id === id;
    });

    const person = {
      ...this.state.persons[personIndex]
    };

    //const person = Object.assign({}, this.state.persons[personIndex])

    person.name = event.target.value;

    const persons = [...this.state.persons];
    persons[personIndex] = person;

    this.setState({persons: persons});
  }

  deletePersonHandler = (personIndex) => {
    //const persons = this.state.persons.splice();
    const persons = [...this.state.persons];
    persons.splice(personIndex, 1);
    this.setState({persons: persons})
  }

  togglePersonHandler = () => {
    const doesShow = this.state.showPersons;
    this.setState({showPersons: !doesShow});
  }

  render() {
    const style = {
      backgroundColor: 'green',
      color: 'white',
      font: 'inherit',
      border: '1px solid blue',
      padding: '8px',
      cursor: 'pointer',
      ':hover': {
        backgroundColor: 'lightgreen',
        color: 'black'
      }
    };

    let persons = null;

    if (this.state.showPersons === true) {
      persons = (
        <div>
          {this.state.persons.map((person, index) => {
            return <Person 
              click={() => this.deletePersonHandler(index)}
              name={person.name} 
              age={person.age}
              key={person.id}
              changed={(event) => this.nameChangedHandler(event, person.id)} />
          })}
        </div>
      );

      style.backgroundColor = 'red';
      style[':hover'] = {
        backgroundColor: 'salmon',
        color: 'black'
      }
    }

    //let classes = ['red', 'bold'].join(' ');
    const classes = [];
    if (this.state.persons.length <= 2) {
      classes.push('red'); //classes = ['red']
    }
    if (this.state.persons.length <= 1) {
      classes.push('bold'); //classes = ['red', 'bold']
    }

    return (
      <StyleRoot>
        <div className="App">
          <h1>Hi, I'm a React App, Wendy</h1>
          <p className={classes.join(' ')}>This is really working!</p>
          <button style={style} onClick={this.togglePersonHandler}>Taggle Persons</button>
          {persons}
        </div>
      </StyleRoot>
    );
    //return React.createElement('div', {classname: 'App'}, React.createElement('h1', null, 'Does this work now?'));
  }
}

export default Radium(App);

Person.js

import React from 'react';
import Radium from 'radium';
import './Person.css';

const person = (props) => {
    const style = {
        '@media (min-width: 500px)': {
            width: '450px'
        }
    };
    return (
        <div className="Person" style={style}>
            <p onClick={props.click}>I'm {props.name} and I am {props.age} years old.</p>
            <p>{props.children}</p>
            <input type="text" onChange={props.changed} value={props.name}/>
        </div>
    )
};

export default Radium(person);

呈現結果:可以有響應式頁面的效果。

問題:但將頁面縮到最小的時候,text輸入框無法呈現效果。

5. Introducing Styled Components & Dynamic Styles

安裝 styled-components:npm install --save styled-components

過程中遇到無法執行程式...

跟著出現的提示照做之後再重新安裝可以正常。

App.js

注意:要用;

const StyledButton = styled.button`

background-color: ${props => props.alt ? 'red' : 'green'};

color: white;

font: inherit;

border: 1px solid blue;

padding: 8px;

cursor: pointer;

 

&:hover {

background-color: ${props => props.alt ? 'salmon' : 'lightgreen'};

color: black;

}

`;

// import logo from './logo.svg';
import React, { Component } from 'react';
import styled from 'styled-components';
import './App.css';
//import Radium, { StyleRoot } from 'radium';
// import { render } from 'react-dom';
import Person from './Person/Person';
//import person from './Person/Person';
//import classes from '*.module.css';

const StyledButton = styled.button`
  background-color: ${props => props.alt ? 'red' : 'green'};
  color: white;
  font: inherit;
  border: 1px solid blue;
  padding: 8px;
  cursor: pointer;
  
  &:hover {
    background-color: ${props => props.alt ? 'salmon' : 'lightgreen'};
    color: black;
  }
`;

//function App() {
class App extends Component{
  state = {
    persons: [
      { id:'asfa1', name:'Max', age: 28 },
      { id:'vasfa1', name:'Manu', age: 29 },
      { id:'asdf1', name:'Stephanie', age: 26 }
    ],
    otherState: 'Some other value',
    showPersons: false
  } 

  nameChangedHandler = (event, id) => {
    const personIndex =this.state.persons.findIndex(p => {
      return p.id === id;
    });

    const person = {
      ...this.state.persons[personIndex]
    };

    //const person = Object.assign({}, this.state.persons[personIndex])

    person.name = event.target.value;

    const persons = [...this.state.persons];
    persons[personIndex] = person;

    this.setState({persons: persons});
  }

  deletePersonHandler = (personIndex) => {
    //const persons = this.state.persons.splice();
    const persons = [...this.state.persons];
    persons.splice(personIndex, 1);
    this.setState({persons: persons})
  }

  togglePersonHandler = () => {
    const doesShow = this.state.showPersons;
    this.setState({showPersons: !doesShow});
  }

  render() {
    const style = {
      backgroundColor: 'green',
      color: 'white',
      font: 'inherit',
      border: '1px solid blue',
      padding: '8px',
      cursor: 'pointer',
      ':hover': {
        backgroundColor: 'lightgreen',
        color: 'black'
      }
    };

    let persons = null;

    if (this.state.showPersons === true) {
      persons = (
        <div>
          {this.state.persons.map((person, index) => {
            return <Person 
              click={() => this.deletePersonHandler(index)}
              name={person.name} 
              age={person.age}
              key={person.id}
              changed={(event) => this.nameChangedHandler(event, person.id)} />
          })}
        </div>
      );

      /*style.backgroundColor = 'red';
      style[':hover'] = {
        backgroundColor: 'salmon',
        color: 'black'
      }*/
    }

    //let classes = ['red', 'bold'].join(' ');
    const classes = [];
    if (this.state.persons.length <= 2) {
      classes.push('red'); //classes = ['red']
    }
    if (this.state.persons.length <= 1) {
      classes.push('bold'); //classes = ['red', 'bold']
    }

    return (
      //<StyleRoot>
        <div className="App">
          <h1>Hi, I'm a React App, Wendy</h1>
          <p className={classes.join(' ')}>This is really working!</p>
          <StyledButton alt={this.state.showPersons} onClick={this.togglePersonHandler}>Taggle Persons</StyledButton>
          {persons}
        </div>
      //</StyleRoot>
    );
    //return React.createElement('div', {classname: 'App'}, React.createElement('h1', null, 'Does this work now?'));
  }
}

//export default Radium(App);
export default App;

Person.js

import React from 'react';
//import Radium from 'radium';
import styled from 'styled-components';
//import './Person.css';

const StyledDiv = styled.div`
    width: 60%;
    margin: 16px auto;
    border: 1px solid #eee;
    box-shadow: 0 2px 3px #ccc;
    padding: 16px;
    text-align: center;

    @media (min-width: 500px) {
        width: 450px;
    }
`;

const person = (props) => {
    /*const style = {
        '@media (min-width: 500px)': {
            width: '450px'
        }
    };*/
    return (
        //<div className="Person" style={style}>
        <StyledDiv>
            <p onClick={props.click}>I'm {props.name} and I am {props.age} years old.</p>
            <p>{props.children}</p>
            <input type="text" onChange={props.changed} value={props.name}/>
        </StyledDiv>
    )
};

//export default Radium(person);
export default person;

Person.css

/*.Person{
    width: 60%;
    margin: 16px auto;
    border: 1px solid #eee;
    box-shadow: 0 2px 3px #ccc;
    padding: 16px;
    text-align: center;
}

@media (min-width: 500px) {
    .Person {
        width: 450px;
    }
}*/

呈現結果:跟前面的結果一樣,只是將原本 CSS 程式部分整理成單一 component 的 CSS。

6. Workong with CSS Modules & Media Queries

npm run eject

git add . 

git commit -m "removed styled components from app.js"

npm run eject

App.js

// import logo from './logo.svg';
import React, { Component } from 'react';
import styled from 'styled-components';
import './App.css';
//import Radium, { StyleRoot } from 'radium';
// import { render } from 'react-dom';
import Person from './Person/Person';
//import person from './Person/Person';
//import classes from '*.module.css';

/*const StyledButton = styled.button`
  background-color: ${props => props.alt ? 'red' : 'green'};
  color: white;
  font: inherit;
  border: 1px solid blue;
  padding: 8px;
  cursor: pointer;
  
  &:hover {
    background-color: ${props => props.alt ? 'salmon' : 'lightgreen'};
    color: black;
  }
`;*/

//function App() {
class App extends Component{
  state = {
    persons: [
      { id:'asfa1', name:'Max', age: 28 },
      { id:'vasfa1', name:'Manu', age: 29 },
      { id:'asdf1', name:'Stephanie', age: 26 }
    ],
    otherState: 'Some other value',
    showPersons: false
  } 

  nameChangedHandler = (event, id) => {
    const personIndex =this.state.persons.findIndex(p => {
      return p.id === id;
    });

    const person = {
      ...this.state.persons[personIndex]
    };

    //const person = Object.assign({}, this.state.persons[personIndex])

    person.name = event.target.value;

    const persons = [...this.state.persons];
    persons[personIndex] = person;

    this.setState({persons: persons});
  }

  deletePersonHandler = (personIndex) => {
    //const persons = this.state.persons.splice();
    const persons = [...this.state.persons];
    persons.splice(personIndex, 1);
    this.setState({persons: persons})
  }

  togglePersonHandler = () => {
    const doesShow = this.state.showPersons;
    this.setState({showPersons: !doesShow});
  }

  render() {
    const style = {
      backgroundColor: 'green',
      color: 'white',
      font: 'inherit',
      border: '1px solid blue',
      padding: '8px',
      cursor: 'pointer',
      ':hover': {
        backgroundColor: 'lightgreen',
        color: 'black'
      }
    };

    let persons = null;

    if (this.state.showPersons === true) {
      persons = (
        <div>
          {this.state.persons.map((person, index) => {
            return <Person 
              click={() => this.deletePersonHandler(index)}
              name={person.name} 
              age={person.age}
              key={person.id}
              changed={(event) => this.nameChangedHandler(event, person.id)} />
          })}
        </div>
      );

      /*style.backgroundColor = 'red';
      style[':hover'] = {
        backgroundColor: 'salmon',
        color: 'black'
      }*/
    }

    //let classes = ['red', 'bold'].join(' ');
    const classes = [];
    if (this.state.persons.length <= 2) {
      classes.push('red'); //classes = ['red']
    }
    if (this.state.persons.length <= 1) {
      classes.push('bold'); //classes = ['red', 'bold']
    }

    return (
      //<StyleRoot>
        <div className="App">
          <h1>Hi, I'm a React App, Wendy</h1>
          <p className={classes.join(' ')}>This is really working!</p>
          <button className="button" onClick={this.togglePersonHandler}>Taggle Persons</button>
          {persons}
        </div>
      //</StyleRoot>
    );
    //return React.createElement('div', {classname: 'App'}, React.createElement('h1', null, 'Does this work now?'));
  }
}

//export default Radium(App);
export default App;

App.css

.App {
  text-align: center;
}

.red {
  color: red;
}

.bold {
  font-weight: bold;
}

.button{
  background-color: green;
  color: white;
  font: inherit;
  border: 1px solid blue;
  padding: 8px;
  cursor: pointer;
}

.button:hover{
    background-color: lightgreen;
    color: black;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

呈現結果:跟前面的結果一樣,只是將 styled-components 的 CSS 程式部分整理成 .css,但是紅色暫時沒有出現,另外執行 npm run eject 後的程式碼跟教學相比看似有些不同了(第 73. 的 6 分鐘左右),後面 73, 74 先大概看過,待處理。