本文相關代碼地址: github 效果展現: react-demo
建立項目文件夾javascript
mkdir react-demo cd react-demo npm init -y
yarn add react react-dom yarn add webpack webpack-cli webpack-dev-server webpack-merge babel-core babel-loader babel-polyfill babel-preset-env babel-preset-react babel-preset-stage-0 cross-env file-loader jsx-loader css-loader style-loader url-loader less less-loader --dev
區分開發環境 development
和生產環境 production
配置
分別建立對應的配置文件
antd-mobile 按需加載
css
yarn add babel-plugin-import -D
module.exports = { presets: ["@babel/preset-env", "@babel/preset-react"], plugins: [ "@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties", ["import", { libraryName: "antd-mobile", style: true }] ] };
webpack 中的 externals 防止將某些 import
的包(package)打包到 bundle 中html
modules.export = { plugins: [ new HtmlWebpackPlugin({ title: 'React Board', files: { // 配置 CDN 引入 js: [ '//unpkg.com/swiper/js/swiper.min.js' ], css: [ '//unpkg.com/swiper/css/swiper.min.css' ] } }) ], externals: { swiper: 'Swiper' } }
index.html 設置:vue
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title><%= htmlWebpackPlugin.options.title %></title> <!-- require cdn assets css --> <% for (var i in htmlWebpackPlugin.options.files.css) { %> <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.files.css[i] %>" /> <% } %> </head> <body> <div id="root"></div> <!-- require cdn assets js --> <% for (var i in htmlWebpackPlugin.options.files.js) { %> <script type="text/javascript" src="<%= htmlWebpackPlugin.options.files.js[i] %>"></script> <% } %> </body> </html>
代碼中使用:java
import Swiper from 'swiper';
使用 postcss-loader 實現 css 轉換
node
// 項目使用的是 less yarn add postcss-less-loader -D
webpack.base.js
配置react
{ test: /\.(css|less)$/, use: [ 'style-loader', 'css-loader', 'less-loader', 'postcss-less-loader' ] }
有兩種轉換方案:webpack
選用該插件對全部的 px 轉換成 vw 視窗尺寸git
yarn add postcss-px-to-viewport -D
項目根目錄下創建 postcss.config.js
github
module.exports = { plugins: { "postcss-px-to-viewport": { viewportWidth: 375, // 視窗的寬度,對應的是咱們設計稿的寬度,Iphone6的通常是375 (xx/375*100vw) viewportHeight: 667, // 視窗的高度,Iphone6的通常是667 unitPrecision: 3, // 指定`px`轉換爲視窗單位值的小數位數(不少時候沒法整除) viewportUnit: "vw", // 指定須要轉換成的視窗單位,建議使用vw selectorBlackList: ['.ignore', '.hairlines'],// 指定不轉換爲視窗單位的類,能夠自定義,能夠無限添加,建議定義一至兩個通用的類名 minPixelValue: 1, // 小於或等於`1px`不轉換爲視窗單位,你也能夠設置爲你想要的值 mediaQuery: false, // 容許在媒體查詢中轉換`px` exclude: /(node_module)/i // 忽略 UI 組件庫 } } }
這個插件是對全部 px 轉換成 rem 尺寸單位
yarn add postcss-plugin-px2rem -D
postcss.config.js
配置:
module.exports = { plugins: { "postcss-plugin-px2rem": { rootValue: 75,// 配合 rem.js 使用 750 的設計稿 unitPrecision: 5, mediaQuery: true, exclude: /(node_module)/i, selectorBlackList: ['html', 'mp-', 'calendar', 'iconfont'], // 在 rem.js 全局做用下,排除指定的文件的影響 propBlackList: ['border'] // 過濾屬性 } } }
須要新建 rem.js
或者直接下載 lib-flexible
const viewportWidth = 750 // 基準大小 const baseSize = 32 // 設置 rem 函數 function setRem() { // 當前頁面寬度相對於 750 寬的縮放比例,可根據本身須要修改。 const scale = document.documentElement.clientWidth / viewportWidth // 設置頁面根節點字體大小 document.documentElement.style.fontSize = (baseSize * Math.min(scale, 2)) + 'px' } // 初始化 setRem() // 改變窗口大小時從新設置 rem window.onresize = function () { setRem() }
在入口文件引入:
// App.js import './utils/rem' // import "./utils/flexible.js"
安裝 eslint 插件
yarn add eslint eslint-plugin-import babel-eslint eslint-plugin-react-hooks -D
根目錄下新建 .eslintrc.js
配置文件
module.exports = { parser: "babel-eslint", plugins: [ "react-hooks" ], rules: { "react-hooks/rules-of-hooks": "error", // 檢查 Hook 的規則 "react-hooks/exhaustive-deps": "error" // 檢查 effect 的依賴 } }
yarn add react-router-dom react-router-config
使用 react-router-config
來簡化路由配置
新建 routes.js 文件
import Home from "@/pages/Home" import Me from "@/pages/Me" import Test from "@/pages/Test" console.log(typeof process.env.API) const routes = [ { path: "/home", exact: true, component: Home }, { path: "/me", exact: true, component: Me }, { path: "/test", exact: true, component: Test } ]; export default routes;
根文件 App.js 中引入路由:
import { renderRoutes } from 'react-router-config' import routes from './routes' import { HashRouter as Router } from 'react-router-dom' import Layouts from "./components/Layouts"; function App() { return ( <Router> <Layouts> {renderRoutes(routes)} </Layouts> </Router> ) } ReactDOM.render(<App />, document.getElementById('root'))
Hook 是什麼?
Hook 是一個特殊的函數,它可讓你「鉤入」 React 的特性。Hook 只能再 Function Component 裏面聲明。
返回一個狀態和一個能夠修改狀態的函數 setter
import React, { useState } from 'react'; import { Button } from "antd-mobile"; function User() { const [user, setUser] = useState('Mondo') return ( <div> <div>{user}</div> <Button type="primary" onClick={e => setUser('imondo.cn')}>改變 State</Button> </div> ) }
替代 Class Component 中 componentDidMount
、componentDidUpdate
、componentWillUnmount
等部分生命週期
import React, { useState, useEffect } from 'react'; function User() { const [user, setUser] = useState('Mondo') useEffect(() => { setTimeout(() => { setUser("js.imondo.cn") }, 2000) }, [user]) // 僅在 user 更改時更新 return ( <div> <div>{user}</div> <Button type="primary" onClick={e => setUser('imondo.cn')}>改變 State</Button> </div> ) }
接收一個 context 對象並返回該 context 的當前值。當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider> 的 value prop 決定。當組件上層最近的 <MyContext.Provider> 更新時,該 Hook 會觸發重渲染。
可用於組件間值傳遞
import React, { useContext } from 'react'; const theme = { color: "red" } const UserContext = React.createContext(theme); function User() { ... return ( <UserContext.Provider value={theme}> <Child/> </UserContext.Provider> ) } function Child() { const theme = useContext(UserContext); return ( <div style={{color: theme.color}}>context</div> ) }
使用格式:useMemo(() => fn, deps)
把「建立」函數和依賴項數組做爲參數傳入 useMemo
,它僅會在某個依賴項改變時才從新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算。能夠看成 vue 中的計算屬性
import React, { useState, useMemo } from 'react'; import { Button } from "antd-mobile"; function User() { const [user, setUser] = useState(1) /* 緩存計算屬性 */ const data = useMemo(() => ({ users: (user + 1) }), [user]); const onChangeUser = (e) => { setUser(+e.target.value); } return ( <UserContext.Provider> <input value={user} onChange={onChangeUser}/> <div>{data.users}</div> <Button type="primary" onClick={e => setUser(user + 1)}>改變 State</Button> </UserContext.Provider> ) }
使用格式:const [state, dispatch] = useReducer(reducer, initialArg, init)
它是 useState 的替代方案,在一些場景使用:
最重要的其實它的寫法和 redux 差很少
import React, { useReducer } from "react"; import { Button } from "antd-mobile"; let initCount = 0; function reducer(state = initCount, action) { switch (action) { case "increment": state++ return state case "decrement": state-- return state default: throw new Error(); } } function User() { const [count, disaptch] = useReducer(reducer, initCount) return ( <UserContext.Provider value={theme}> <div>useReducer</div> <div>計數器{count}</div> <Button type="primary" onClick={e => disaptch("decrement")}>減</Button> <Button type="primary" onClick={e => disaptch("increment")}>加</Button> </UserContext.Provider> ) }
返回一個可變的 ref 對象,其 .current
屬性被初始化爲傳入的參數
若是想要訪問子組件內的 ref 對象,子組件須要用 class 聲明組件。
import React, { useState, useMemo, useRef } from 'react'; function Parent() { let [count, setCount] = useState(0) const childRef = useRef(null) const childClick = (val) => { childRef.current.setState({ num: 2 }); } return ( <div> <h4>組件傳值</h4> <button onClick={childClick}>向子組件傳值</button> <Child1 ref={childRef} /> </div> ); } class Child1 extends React.Component { constructor() { super(...arguments); this.state = { num: 1 } } render() { const { num } = this.state; return ( <div> <div>ref 組件</div> <div>{num}</div> </div> ) } }
參考:
React Hooks 最佳實踐
寫React Hooks前必讀
歡迎關注公衆號,你們一塊兒共同交流和進步。