環境建置:create-react-app
環境建置分兩種:
- 自己從頭開始做(webpack + babel)
- 用現成的
接下來,會用現成的 create-react-app (這是 react 官方提供的)來做環境建置
安裝完後,可以用 npx create-react-app -V
來查詢版本
<React.StrictMode>
會幫我檢查是否有用到不該用的東西,但是因為它需要偵測,會造成 console.log 出來的結果跟預期中的不一樣,所以建議是把 <React.StrictMode>
先移除
初探 React 中的 style
方式一:inline style(這是 create-react-app 內建的方式)
style={}
大括號裡面要傳入一個物件
const titleStyle = {
color: 'red',
textAlign: 'center'
}
function Title({ size }) {
if (size === 'XL') {
return <h1>Hello</h1>
}
return (
<h2 style={titleStyle}>Hello</h2>
)
}
比較常見的寫法是把物件直接寫在 style={}
裡面:
function Title({ size }) {
if (size === 'XL') {
return <h1>Hello</h1>
}
return (
<h2 style={{
color: 'red',
textAlign: 'center'
}}>Hello</h2>
)
}
方式二:用 className
和 css(這是 create-react-app 內建的方式)
在 JSX 語法裡面,要寫 className
,不能寫 class
,因為 class
是 JS 的保留字
因此,用 JSX 寫:
<div className="App"></div>
render 到畫面上時,就會是:
<div class="App"></div>
接下來,因為有用 webpack 的關係,我可以直接在 App.js 裡面 import App.css
import './App.css';
在 App.css 裡面,我就可以寫 css 的內容了
方式三:用 styled components 套件(推薦使用!)
styled components 是用 tagged template literal 建立出一套寫 css 的方法
參考文章 JavaScript ES6 中的模版字符串(template literals)和標籤模版(tagged template)
styled components 直接支援 scss, sass 的寫法
styled components 也會自動幫我加上各種 broswer 的 prefix,所以不用擔心兼容性的問題
首先,要先把套件安裝起來
安裝好後,就在 App.js 上方引入 styled components:
import styled from 'styled-components';
接著,就可以開始幫 component 加上 style 了
範例一:幫 <Description>
這個 component 加上 style
Description
就是一個「擁有這些 style 的一個 <p>
component」
const Description = styled.p`
color: salmon;
font-weight: bold;
border: 1px solid green;
`
function App() {
return (
<div className="App">
<Title size="M" />
<Description>
I love you!
</Description>
</div>
);
}
render 之後就會看到:
styled components 幫我做的事情就是:自動產生一個像亂碼的 className 放到元素上,並且把我寫的 css 套用上去
範例二:
const TitleWrapper = styled.h2`
color: teal;
&:hover {
color: orange;
}
span {
color: purple;
}
`
function Title({ size }) {
return (
<TitleWrapper>Hello<span>yoyoyo</span></TitleWrapper>
)
}
範例三:
const TodoItemWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
border: 1px solid black;
`
const TodoContent = styled.div`
color: salmon;
font-size: ${props => props.size === 'XL' ? '20px' : '12px'}
`
const TodoButtonWrapper = styled.div``
const Button = styled.button`
padding: 5px;
color: teal;
&:hover {
color: red;
}
& + & {
margin-left: 10px;
}
`
function App() {
return (
<div className="App">
<TodoItemWrapper>
<TodoContent size="XL">I am a todo</TodoContent>
<TodoButtonWrapper>
<Button>已完成</Button>
<Button>刪除</Button>
</TodoButtonWrapper>
</TodoItemWrapper>
</div>
);
}
styled components 傳 props
參數的方式
props
就像是自定義的「HTML 元素的屬性」
範例一:
在 <TodoContent>
傳入 size="XL"
這個 prop
在 styled component 就可以用 ${props => props.size ...}
來拿到 size="XL"
這個 prop
const TodoContent = styled.div`
color: salmon;
font-size: ${props => props.size === 'XL' ? '20px' : '12px'}
`
function App() {
return (
<div className="App">
<TodoItemWrapper>
<TodoContent size="XL">I am a todo</TodoContent>
<TodoButtonWrapper>
<Button>已完成</Button>
<Button>刪除</Button>
</TodoButtonWrapper>
</TodoItemWrapper>
</div>
);
}
範例二:利用「短路運算子」
<TodoContent>
的 font-size
預設是 12px
如果 <TodoContent>
的 size="XL"
的話,就會用 font-size: 20px
覆蓋掉原本的 font-size: 12px
const TodoContent = styled.div`
color: salmon;
font-size: 12px;
${props => props.size === 'XL' && `
font-size: 20px;
`}
`
function App() {
return (
<div className="App">
<TodoItemWrapper>
<TodoContent size="XL">I am a todo</TodoContent>
<TodoButtonWrapper>
<Button>已完成</Button>
<Button>刪除</Button>
</TodoButtonWrapper>
</TodoItemWrapper>
</div>
);
}
範例三:
在 <Nav>
加上 $active
這個屬性
在 styled component 就可以做出:如果 $active
是 true,才會有 background-color
const Nav = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100px;
cursor: pointer;
// 如果 $active 是 true,才會有 background-color
${(props) =>
props.$active &&
`
background-color: rgba(202, 68, 111, 0.1);
`}
`;
export default function Header() {
return (
<HeaderContainer>
<LeftContainer>
<Brand>我的第一個部落格</Brand>
<NavbarList>
<Nav $active>首頁</Nav> {/* 加上 $active 這個屬性 */}
<Nav>發布文章</Nav>
</NavbarList>
</LeftContainer>
<NavbarList>
<Nav>登入</Nav>
</NavbarList>
</HeaderContainer>
);
}
改寫為:用 react 的 component,裡面再接 styled components
const TodoItemWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
border: 1px solid black;
`
const TodoContent = styled.div`
color: salmon;
font-size: 12px;
${props => props.size === 'XL' && `
font-size: 20px;
`}
`
const TodoButtonWrapper = styled.div``
const Button = styled.button`
padding: 5px;
color: teal;
&:hover {
color: red;
}
& + & {
margin-left: 10px;
}
`
function TodoItem({ size, content }){
return (
<TodoItemWrapper>
<TodoContent size={size}>{content}</TodoContent>
<TodoButtonWrapper>
<Button>已完成</Button>
<Button>刪除</Button>
</TodoButtonWrapper>
</TodoItemWrapper>
)
}
function App() {
return (
<div className="App">
<TodoItem content={123} />
<TodoItem content={456} size='XL' />
</div>
);
}
對 styled component 做 restyle
把 Button
做 restyle 變成 BlueButton
const Button = styled.button`
padding: 5px;
color: teal;
&:hover {
color: red;
}
& + & {
margin-left: 10px;
}
`
const BlueButton = styled(Button)`
color: blue;
`
function TodoItem({ size, content }){
return (
<TodoItemWrapper>
<TodoContent size={size}>{content}</TodoContent>
<TodoButtonWrapper>
<BlueButton>已完成</BlueButton>
<Button>刪除</Button>
</TodoButtonWrapper>
</TodoItemWrapper>
)
}
對 react component 做 restyle:要接收一個 className
參數
因為 style components 是透過「傳 className」,所以要給 react component 一個 className
的參數,並把參數放在我想要 restyle 的元素上
function TodoItem({ className, size, content }){
return (
<TodoItemWrapper className={className}>
<TodoContent size={size}>{content}</TodoContent>
<TodoButtonWrapper>
<BlueButton>已完成</BlueButton>
<Button>刪除</Button>
</TodoButtonWrapper>
</TodoItemWrapper>
)
}
const YellowTodoItem = styled(TodoItem)`
background-color: yellow;
`
function App() {
return (
<div className="App">
<TodoItem content={123} />
<YellowTodoItem content={456} size='XL' />
</div>
);
}
style components 會自動幫 background-color: yellow
產生一個新的 className,並且把這個 className 加在 <TodoItemWrapper>
上面
media query
就跟一般在寫 css 時的 media query 用法是一樣的:
const Button = styled.button`
padding: 5px;
color: teal;
font-size: 20px;
@media (min-width: 768px) {
font-size: 16px;
}
&:hover {
color: red;
}
& + & {
margin-left: 10px;
}
`
把 media query 的斷點寫在另一個檔案
在實務上,都會建立一個 constants 資料夾,在裡面新建一個檔案叫做 style.js
然後,就可以把 media query 的斷點都寫在 style.js 裡面做 export:
export const MEDIA_QUERY_MD = `@media (min-width: 768px)`;
export const MEDIA_QUERY_LG = `@media (min-width: 1000px)`;
在 App.js 要用時引入即可,這樣程式碼會比較乾淨
import { MEDIA_QUERY_MD, MEDIA_QUERY_LG} from './constants/style';
const Button = styled.button`
padding: 5px;
color: teal;
font-size: 20px;
${MEDIA_QUERY_MD} {
font-size: 16px;
}
${MEDIA_QUERY_LG} {
font-size: 12px;
}
&:hover {
color: red;
}
& + & {
margin-left: 10px;
}
`
在 styled components 使用變數
使用變數的做法是:傳一個 global 的參數,這樣在其他檔案就可以直接使用
首先,要在最上層的檔案 index.js 引入 ThemeProvider
<ThemeProvider></ThemeProvider>
是一個 component
然後,用 <ThemeProvider></ThemeProvider>
把我要 render 的 <App />
包住
在 <ThemeProvider></ThemeProvider>
要接收一個 theme
參數,在 theme
裡面就可以自己定義我想要的變數,例如 colors
import { ThemeProvider } from 'styled-components';
const theme = {
colors: {
primary_300: '#ef9898',
primary_400: '#ec6b6b',
primary_500: '#ea1212',
}
}
ReactDOM.render(
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
,
document.getElementById('root')
);
接著,在 App.js 我就可以用 props.theme.colors.primary_500
來拿到 primary_500: '#ea1212'
這個顏色
const TodoContent = styled.div`
color: ${props => props.theme.colors.primary_500};
font-size: 12px;
${props => props.size === 'XL' && `
font-size: 20px;
`}
`
把 component 獨立成不同的檔案
當 component 越來越多時,就可以把不同的 component 獨立成不同的檔案
例如
新建一個檔案叫做 TodoItem.js,裡面就只放 TodoItem
這個 component,然後把它 export
通常都會用 export default
來 export component
import styled from 'styled-components';
import { MEDIA_QUERY_MD, MEDIA_QUERY_LG} from './constants/style';
const TodoItemWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
border: 1px solid black;
margin-bottom: 10px;
`
const TodoContent = styled.div`
color: ${props => props.theme.colors.primary_500};
font-size: 12px;
${props => props.size === 'XL' && `
font-size: 20px;
`}
`
const TodoButtonWrapper = styled.div``
const Button = styled.button`
padding: 5px;
color: teal;
font-size: 20px;
${MEDIA_QUERY_MD} {
font-size: 16px;
}
${MEDIA_QUERY_LG} {
font-size: 12px;
}
&:hover {
color: red;
}
& + & {
margin-left: 10px;
}
`
const BlueButton = styled(Button)`
color: blue;
`
export default function TodoItem({ className, size, content }){
return (
<TodoItemWrapper className={className}>
<TodoContent size={size}>{content}</TodoContent>
<TodoButtonWrapper>
<BlueButton>已完成</BlueButton>
<Button>刪除</Button>
</TodoButtonWrapper>
</TodoItemWrapper>
)
}
在 App.js 就可以引入 TodoItem
拿來用
import styled from 'styled-components';
import TodoItem from './TodoItem';
const YellowTodoItem = styled(TodoItem)`
background-color: yellow;
`
function App() {
return (
<div className="App">
<TodoItem content={123} />
<YellowTodoItem content={456} size='XL' />
</div>
);
}
export default App;
一探 JSX 背後的秘密
當我們在寫 JSX 的語法時,背後是透過 Babel 幫我們轉成 JavaScript 的程式碼
JSX:
<Button size="XL">hello</Button>
轉成 JavaScript 的程式碼:
React.createElement(Button, {
size: "XL"
}, "hello");
初探 state
在 React 裡面,如果要用 state 的話,要在 App.js 寫這行:
import React from 'react';
從 React 16.8 之後,出現了 hooks 這個新的用法
import styled from 'styled-components';
import TodoItem from './TodoItem';
import React from 'react';
const YellowTodoItem = styled(TodoItem)`
background-color: yellow;
`
function App() {
const [counter, setCounter] = React.useState(0);
const handleButtonClick = () => {
setCounter(counter + 1)
}
return (
<div className="App">
counter: {counter}
<button onClick={handleButtonClick}>increment</button>
<TodoItem content={123} />
<YellowTodoItem content={456} size='XL' />
</div>
);
}
export default App;
React.useState()
是一個 hook,透過這個 hook 讓我們可以在 function component 裡面使用一個 state
React.useState()
會回傳一個陣列:
- 陣列裡面的第一個值是「state 的值」
- state 的初始值要寫在
React.useState()
小括號裡面
- state 的初始值要寫在
- 陣列裡面的第二個值
setCounter
是一個 function,呼叫setCounter
這個 function 來更新 state
const [counter, setCounter] = React.useState(0)
是「解構」的寫法
可以看成是這樣:
用「解構」拿出來的 a 就會是 123,b 就會是 456
function useState() {
return [123, 456]
}
const [a, b] = useState()
output:
點擊 button,counter 就會 +1
React 的這個流程是:
打開網頁後會做第一次的 render,render 就是指:React 會去執行 App()
這個 function,把 App()
return 的東西放到畫面上(這個動作叫做 mount,也就是把東西放到 DOM 上面,讓瀏覽器 render 出來)
每當我更新 state,React 就會 re-render,也就是「會再呼叫一次 App()
這個 function」