npx create-react-app your-app
Prettier - Code formattercss
或者右下角語言選JavascriptReact或者TypescriptReacthtml
https://blog.csdn.net/weixin_40461281/article/details/79964659node
{ }
值得注意的是有一些 「falsy」 值,如數字 0,仍然會被 React 渲染。例如,如下代碼並不會像你預期那樣工做,由於當 props.messages 是空數組時,0 仍然會被渲染:react
<div> {props.messages.length && <MessageList messages={props.messages} /> } </div>
要解決這個問題,確保 && 以前的表達式老是布爾值:webpack
<div> {props.messages.length > 0 && <MessageList messages={props.messages} /> } </div>
State 的更新多是異步的ios
出於性能考慮,React 可能會把多個 setState() 調用合併成一個調用。git
由於 this.props 和 this.state 可能會異步更新,因此你不要依賴他們的值來更新下一個狀態。github
可讓 setState() 接收一個函數而不是一個對象。這個函數用上一個 state 做爲第一個參數,將這次更新被應用時的 props 作爲第二個參數web
// Wrong this.setState({ counter: this.state.counter + this.props.increment, }); // Correct this.setState((state, props) => ({ counter: state.counter + props.increment }));
在 map() 方法中的元素須要設置 key 屬性。typescript
受控組件
class EssayForm extends React.Component { constructor(props) { super(props); this.state = { value: '請撰寫一篇關於你喜歡的 DOM 元素的文章.' }; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('提交的文章: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> 文章: <textarea value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="提交" /> </form> ); } }
非受控組件
但願 React 能賦予組件一個初始值,可是不去控制後續的更新。 在這種狀況下, 你能夠指定一個 defaultValue 屬性,而不是 value。
class NameForm extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); this.input = React.createRef(); } handleSubmit(event) { alert('A name was submitted: ' + this.input.current.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input defaultValue="Bob" type="text" ref={this.input} /> </label> <input type="submit" value="Submit" /> </form> ); } }
在 React 應用中,任何可變數據應當只有一個相對應的惟一「數據源」。一般,state 都是首先添加到須要渲染數據的組件中去。而後,若是其餘組件也須要這個 state,那麼你能夠將它提高至這些組件的最近共同父組件中。你應當依靠自上而下的數據流,而不是嘗試在不一樣組件間同步 state。
const OtherComponent = React.lazy(() => import('./OtherComponent'));
而後應在 Suspense 組件中渲染 lazy 組件,如此使得咱們可使用在等待加載 lazy 組件時作優雅降級(如 loading 指示器等)。
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import React, { Suspense, lazy } from 'react'; const Home = lazy(() => import('./routes/Home')); const About = lazy(() => import('./routes/About')); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> </Switch> </Suspense> </Router> );
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // 更新 state 使下一次渲染可以顯示降級後的 UI return { hasError: true }; } componentDidCatch(error, errorInfo) { // 你一樣能夠將錯誤日誌上報給服務器 logErrorToMyService(error, errorInfo); } render() { if (this.state.hasError) { // 你能夠自定義降級後的 UI 並渲染 return <h1>Something went wrong.</h1>; } return this.props.children; } }
而後你能夠將它做爲一個常規組件去使用
<ErrorBoundary> <MyWidget /> </ErrorBoundary>
Context 提供了一個無需爲每層組件手動添加 props,就能在組件樹間進行數據傳遞的方法。頂層建立context
theme-context.js
export const themes = { light: { foreground: '#000000', background: '#eeeeee', }, dark: { foreground: '#ffffff', background: '#222222', }, }; export const ThemeContext = React.createContext( themes.dark // 默認值 );
themed-button.js
import {ThemeContext} from './theme-context'; class ThemedButton extends React.Component { render() { let props = this.props; let theme = this.context; return ( <button {...props} style={{backgroundColor: theme.background}} /> ); } } ThemedButton.contextType = ThemeContext; export default ThemedButton;
app.js
import {ThemeContext, themes} from './theme-context'; import ThemedButton from './themed-button'; // 一個使用 ThemedButton 的中間組件 function Toolbar(props) { return ( <ThemedButton onClick={props.changeTheme}> Change Theme </ThemedButton> ); } class App extends React.Component { constructor(props) { super(props); this.state = { theme: themes.light, }; this.toggleTheme = () => { this.setState(state => ({ theme: state.theme === themes.dark ? themes.light : themes.dark, })); }; } render() { // 在 ThemeProvider 內部的 ThemedButton 按鈕組件使用 state 中的 theme 值, // 而外部的組件使用默認的 theme 值 return ( <Page> <ThemeContext.Provider value={this.state.theme}> <Toolbar changeTheme={this.toggleTheme} /> </ThemeContext.Provider> <Section> <ThemedButton /> </Section> </Page> ); } } ReactDOM.render(<App />, document.root);
在 ThemeProvider 內部的 ThemedButton 按鈕組件使用 state 中的 theme 值,
而外部的組件使用默認的 theme 值
頂部組件
import React, { Component } from 'react' import Welcome from './components/welcome'; import TodoList from './components/TodoList'; import './App.css'; import {themes, ThemeContext} from './config/context' export default class App extends Component { constructor(props) { super(props); // 改變state的方法 傳遞給context this.toggleTheme = (change) => { this.setState((state)=> (change)) } this.state = { date: 123, mark: false, theme: themes.light, toggleTheme: this.toggleTheme } } render() { return ( <ThemeContext.Provider value={this.state}> <div className="App"> {this.state.date} <header className="App-header"> <Welcome /> <TodoList /> </header> </div> </ThemeContext.Provider> ) } }
內部組件
import React, { Component } from 'react' import {themes, ThemeContext} from '../config/context' class Model extends Component { static contextType = ThemeContext constructor(props) { super(props) this.state = {} } toggle = () => { // 修改 頂部state this.context.toggleTheme({ theme: themes.dark, date: 111111111111 }) } render() { return ( <div> {this.context.theme.background} <button onClick={this.toggle}>toggle</button> </div> ) } } export default Model
你能夠經過 context 傳遞一個函數,使得 consumers 組件更新 context:
theme-context.js
// 確保傳遞給 createContext 的默認值數據結構是調用的組件(consumers)所能匹配的! export const ThemeContext = React.createContext({ theme: themes.dark, toggleTheme: () => {}, });
theme-toggler-button.js
import {ThemeContext} from './theme-context'; function ThemeTogglerButton() { // Theme Toggler 按鈕不只僅只獲取 theme 值,它也從 context 中獲取到一個 toggleTheme 函數 return ( <ThemeContext.Consumer> {({theme, toggleTheme}) => ( <button onClick={toggleTheme} style={{backgroundColor: theme.background}}> Toggle Theme </button> )} </ThemeContext.Consumer> ); } export default ThemeTogglerButton;
app.js
import {ThemeContext, themes} from './theme-context'; import ThemeTogglerButton from './theme-toggler-button'; class App extends React.Component { constructor(props) { super(props); this.toggleTheme = () => { this.setState(state => ({ theme: state.theme === themes.dark ? themes.light : themes.dark, })); }; // State 也包含了更新函數,所以它會被傳遞進 context provider。 this.state = { theme: themes.light, toggleTheme: this.toggleTheme, }; } render() { // 整個 state 都被傳遞進 provider return ( <ThemeContext.Provider value={this.state}> <Content /> </ThemeContext.Provider> ); } } function Content() { return ( <div> <ThemeTogglerButton /> </div> ); } ReactDOM.render(<App />, document.root);
視圖層框架 react
setState( , cb) 第二個參數 回調函數中Dom已更新
ref 獲取 Dom節點
回調函數裏參數獲取是Dom節點
<input type="number" name="num" id="" value={this.state.num} onChange={this.setVal} ref={(input) => { this.input = input }} />
this.file = React.createRef() <input type="file" ref={this.file}/>
當你在一個模塊中導出許多 React 組件時,這會很是方便。例如,若是 MyComponents.DatePicker 是一個組件,你能夠在 JSX 中直接使用:
import React from 'react'; const MyComponents = { DatePicker: function DatePicker(props) { return <div>Imagine a {props.color} datepicker here.</div>; } } function BlueDatePicker() { return <MyComponents.DatePicker color="blue" />; }
shouldComponentUpdate(nextProps, nextState) { if (this.props.color !== nextProps.color) { return true; } if (this.state.count !== nextState.count) { return true; } return false; }
class CounterButton extends React.PureComponent { }
CSSTransition
appear 入場動畫
unmountOnExit 動畫結束關閉 none
鉤子函數 onEnter onEntering onEntered (onExit)
// 此函數接收一個組件... function withSubscription(WrappedComponent, selectData) { // ...並返回另外一個組件... return class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { data: selectData(DataSource, props) }; } componentDidMount() { // ...負責訂閱相關的操做... DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ data: selectData(DataSource, this.props) }); } render() { // ... 並使用新數據渲染被包裝的組件! // 請注意,咱們可能還會傳遞其餘屬性 return <WrappedComponent data={this.state.data} {...this.props} />; } }; }
function withSubscription(WrappedComponent) { class WithSubscription extends React.Component {/* ... */} WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`; return WithSubscription; } function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; }
術語 「render prop」 是指一種在 React 組件之間使用一個值爲函數的 prop 共享代碼的簡單技術
class Cat extends React.Component { render() { const mouse = this.props.mouse; return ( <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} /> ); } } class MouseWithCat extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {/* 咱們能夠在這裏換掉 <p> 的 <Cat> ...... 可是接着咱們須要建立一個單獨的 <MouseWithSomethingElse> 每次咱們須要使用它時,<MouseWithCat> 是否是真的能夠重複使用. */} <Cat mouse={this.state} /> </div> ); } } class MouseTracker extends React.Component { render() { return ( <div> <h1>移動鼠標!</h1> <MouseWithCat /> </div> ); } }
注意事項: 將 Render Props 與 React.PureComponent 一塊兒使用時要當心
若是你在 render 方法裏建立函數,那麼使用 render prop 會抵消使用 React.PureComponent 帶來的優點。由於淺比較 props 的時候總會獲得 false,而且在這種狀況下每個 render 對於 render prop 將會生成一個新的值。
class Mouse extends React.PureComponent { // 與上面相同的代碼...... } class MouseTracker extends React.Component { render() { return ( <div> <h1>Move the mouse around!</h1> {/* 這是很差的! 每一個渲染的 `render` prop的值將會是不一樣的。 每次返回props.mouse是新函數 */} <Mouse render={mouse => ( <Cat mouse={mouse} /> )}/> </div> ); } }
爲了繞過這一問題,有時你能夠定義一個 prop 做爲實例方法,相似這樣:
class MouseTracker extends React.Component { // 定義爲實例方法,`this.renderTheCat`始終 // 當咱們在渲染中使用它時,它指的是相同的函數 renderTheCat(mouse) { return <Cat mouse={mouse} />; } render() { return ( <div> <h1>Move the mouse around!</h1> <Mouse render={this.renderTheCat} /> </div> ); } }
import PropTypes from 'prop-types'; MyComponent.propTypes = { // 你能夠將屬性聲明爲 JS 原生類型,默認狀況下 // 這些屬性都是可選的。 optionalArray: PropTypes.array, optionalBool: PropTypes.bool, optionalFunc: PropTypes.func, optionalNumber: PropTypes.number, optionalObject: PropTypes.object, optionalString: PropTypes.string, optionalSymbol: PropTypes.symbol, // 任何可被渲染的元素(包括數字、字符串、元素或數組) // (或 Fragment) 也包含這些類型。 optionalNode: PropTypes.node, // 一個 React 元素。 optionalElement: PropTypes.element, // 一個 React 元素類型(即,MyComponent)。 optionalElementType: PropTypes.elementType, // 你也能夠聲明 prop 爲類的實例,這裏使用 // JS 的 instanceof 操做符。 optionalMessage: PropTypes.instanceOf(Message), // 你可讓你的 prop 只能是特定的值,指定它爲 // 枚舉類型。 optionalEnum: PropTypes.oneOf(['News', 'Photos']), // 一個對象能夠是幾種類型中的任意一個類型 optionalUnion: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, PropTypes.instanceOf(Message) ]), // 能夠指定一個數組由某一類型的元素組成 optionalArrayOf: PropTypes.arrayOf(PropTypes.number), // 能夠指定一個對象由某一類型的值組成 optionalObjectOf: PropTypes.objectOf(PropTypes.number), // 能夠指定一個對象由特定的類型值組成 optionalObjectWithShape: PropTypes.shape({ color: PropTypes.string, fontSize: PropTypes.number }), // An object with warnings on extra properties optionalObjectWithStrictShape: PropTypes.exact({ name: PropTypes.string, quantity: PropTypes.number }), // 你能夠在任何 PropTypes 屬性後面加上 `isRequired` ,確保 // 這個 prop 沒有被提供時,會打印警告信息。 requiredFunc: PropTypes.func.isRequired, // 任意類型的數據 requiredAny: PropTypes.any.isRequired, // 你能夠指定一個自定義驗證器。它在驗證失敗時應返回一個 Error 對象。 // 請不要使用 `console.warn` 或拋出異常,由於這在 `onOfType` 中不會起做用。 customProp: function(props, propName, componentName) { if (!/matchme/.test(props[propName])) { return new Error( 'Invalid prop `' + propName + '` supplied to' + ' `' + componentName + '`. Validation failed.' ); } }, // 你也能夠提供一個自定義的 `arrayOf` 或 `objectOf` 驗證器。 // 它應該在驗證失敗時返回一個 Error 對象。 // 驗證器將驗證數組或對象中的每一個值。驗證器的前兩個參數 // 第一個是數組或對象自己 // 第二個是他們當前的鍵。 customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) { if (!/matchme/.test(propValue[key])) { return new Error( 'Invalid prop `' + propFullName + '` supplied to' + ' `' + componentName + '`. Validation failed.' ); } }) };
React.memo
React.memo 爲高階組件。它與 React.PureComponent 很是類似,但它適用於函數組件,但不適用於 class 組件。
const MyComponent = React.memo(function MyComponent(props) { /* 使用 props 渲染 */ });
默認狀況下其只會對複雜對象作淺層對比,若是你想要控制對比過程,那麼請將自定義的比較函數經過第二個參數傳入來實現。
function MyComponent(props) { /* 使用 props 渲染 */ } function areEqual(prevProps, nextProps) { /* 若是把 nextProps 傳入 render 方法的返回結果與 將 prevProps 傳入 render 方法的返回結果一致則返回 true, 不然返回 false */ } export default React.memo(MyComponent, areEqual);
yarn add redux
https://github.com/zalmoxisus/redux-devtools-extension#installation
開啓redux-devtools
const store = createStore( reducer, /* preloadedState, */ + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );
import { createStore } from 'redux' /** * 這是一個 reducer,形式爲 (state, action) => state 的純函數。 * 描述了 action 如何把 state 轉變成下一個 state。 * * state 的形式取決於你,能夠是基本類型、數組、對象、 * 甚至是 Immutable.js 生成的數據結構。唯一的要點是 * 當 state 變化時須要返回全新的對象,而不是修改傳入的參數。 * * 下面例子使用 `switch` 語句和字符串來作判斷,但你能夠寫幫助類(helper) * 根據不一樣的約定(如方法映射)來判斷,只要適用你的項目便可。 */ function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 default: return state } } // 建立 Redux store 來存放應用的狀態。 // API 是 { subscribe, dispatch, getState }。 let store = createStore(counter) // 能夠手動訂閱更新,也能夠事件綁定到視圖層。 store.subscribe(() => console.log(store.getState())) // 改變內部 state 唯一方法是 dispatch 一個 action。 // action 能夠被序列化,用日記記錄和儲存下來,後期還能夠以回放的方式執行 store.dispatch({ type: 'INCREMENT' }) // 1
import React, { PureComponent } from 'react' import { List, Typography, Button } from 'antd'; import store from '../store/index' export default class Antd extends PureComponent { constructor(props){ super(props) this.state = { data: store.getState().list } // store變化監聽回調 store.subscribe(() => this.handChangeState()) } add = (li) => { console.log(li) store.dispatch({type: 'ADD_LIST', payload: li}) } // 更新視圖 handChangeState = () => { this.setState({ data: store.getState().list }) } render() { return ( <div> <Button onClick={this.add.bind(this, 'Los Angeles battles huge wildfires.')}>add</Button> <h3 style={{ marginBottom: 16 }}>Default Size</h3> <List header={<div>Header</div>} footer={<div>Footer</div>} bordered dataSource={this.state.data} renderItem={item => ( <List.Item> <Typography.Text mark>[ITEM]</Typography.Text> {item} </List.Item> )} /> </div> ) } }
對於大的應用來講,不大可能僅僅只寫一個這樣的函數,因此咱們編寫不少小函數來分別管理 state 的一部分:
書寫錯誤時會有提示.
// 報錯提示 const ADD_LIST = 'ADD_LIST'
生成 action creator 的函數:減小多餘的樣板代碼
function makeActionCreator(type, ...argNames) { return function(...args) { const action = { type } argNames.forEach((arg, index) => { action[argNames[index]] = args[index] }) return action } } const ADD_TODO = 'ADD_TODO' const EDIT_TODO = 'EDIT_TODO' const REMOVE_TODO = 'REMOVE_TODO' export const addTodo = makeActionCreator(ADD_TODO, 'text') export const editTodo = makeActionCreator(EDIT_TODO, 'id', 'text') export const removeTodo = makeActionCreator(REMOVE_TODO, 'id')
import { combineReducers } from 'redux' export default combineReducers({ visibilityFilter, todos })
上面的寫法和下面徹底等價:
export default function todoApp(state = {}, action) { return { visibilityFilter: visibilityFilter(state.visibilityFilter, action), todos: todos(state.todos, action) } }
Store 有如下職責:
createStore() 的第二個參數是可選的, 用於設置 state 初始狀態 使用服務器state時
使用 connect() 前,須要先定義 mapStateToProps 這個函數來指定如何把當前 Redux store state 映射到展現組件的 props 中。
const mapStateToProps = state => ({ data: state.list })
除了讀取 state,容器組件還能分發 action。相似的方式,能夠定義 mapDispatchToProps() 方法接收 dispatch() 方法並返回指望注入到展現組件的 props 中的回調方法
const mapDispatchToProps = (dispatch) => { return { addList: (li) => { dispatch(addList(li)) } } }
connect()
import React, { PureComponent } from 'react' import { connect } from 'react-redux' import { List, Typography, Button } from 'antd'; import { addList } from '../store/actionCreators'; class Antd extends PureComponent { add = (li) => { console.log(li) // 修改store this.props.addList(li) } render() { const {data} = this.props return ( <div> <Button onClick={this.add.bind(this, 'Los Angeles battles huge wildfires.')}>add</Button> <h3 style={{ marginBottom: 16 }}>Default Size</h3> <List header={<div>Header</div>} footer={<div>Footer</div>} bordered dataSource={data} renderItem={item => ( <List.Item> <Typography.Text mark>[ITEM]</Typography.Text> {item} </List.Item> )} /> </div> ) } } const mapStateToProps = state => ({ data: state.list }) const mapDispatchToProps = (dispatch) => { return { addList: (li) => { dispatch(addList(li)) } } } export default connect( mapStateToProps, mapDispatchToProps )(Antd)
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import {Provider} from 'react-redux' import store from './store' // jsx ReactDOM.render( <Provider store={store}> < App / > </Provider>, document.getElementById('root') );
<容器組件> <展現組件/> <容器組件/> <其它組件/>
store.js
import { compose, createStore, applyMiddleware } from 'redux' // redux中間件 import thunk from 'redux-thunk' import reducer from './reducer' const middleware = [thunk] // 建立 Redux store 來存放應用的狀態。 // API 是 { subscribe, dispatch, getState }。 // redux-devtools-extension const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore( reducer, /* preloadedState, */ composeEnhancers( applyMiddleware(...middleware), ) ) // 注意 subscribe() 返回一個函數用來註銷監聽器 store.subscribe(() => console.log(store.getState())) // 改變內部 state 唯一方法是 dispatch 一個 action。 // action 能夠被序列化,用日記記錄和儲存下來,後期還能夠以回放的方式執行 store.dispatch({type: 'INCREMENT'}) // 中止監聽 state 更新 // unsubscribe() export default store
組件中
import React, { PureComponent } from 'react' import { connect } from 'react-redux' import { List, Typography, Button } from 'antd'; import { addList, initList} from '../store/tolist/actions'; class Antd extends PureComponent { componentDidMount() { this.props.initList() } add = (li) => { // 修改store this.props.addList(li) } render() { const {data} = this.props return ( <div> <Button onClick={this.add.bind(this, 'Los Angeles battles huge wildfires.')}>add</Button> <h3 style={{ marginBottom: 16 }}>Default Size</h3> <List header={<div>Header</div>} footer={<div>Footer</div>} bordered dataSource={data} renderItem={item => ( <List.Item> <Typography.Text mark>[ITEM]</Typography.Text> {item} </List.Item> )} /> </div> ) } } const mapStateToProps = state => ({ data: state.list }) // const mapDispatchToProps = (dispatch) => { // return { // addList: (li) => { // dispatch(addList(li)) // }, // initList: (list) => { // dispatch(initList(list)) // } // } // } export default connect( mapStateToProps, { addList, initList } )(Antd)
actions.js
import {ADD_LIST, INIT_LIST} from './actionTypes' import axios from 'axios' // 幫助生成 action creator function makeActionCreator(type, ...argNames) { return function(...args) { const action = { type } argNames.forEach((arg, index) => { action[argNames[index]] = args[index] }) return action } } // 統一管理 action export const addList = makeActionCreator(ADD_LIST, 'payload') // Action Creator(動做生成器),返回一個函數 redux-thunk中間件,改造store.dispatch,使得後者能夠接受函數做爲參數。 export const initList = () => (dispatch) => { axios.get('http://localhost.charlesproxy.com:3000/api/list').then((res) => { console.log(res.data) dispatch({ type: INIT_LIST, list: res.data }) }).catch((res) => { console.log(res) }) }
saga.js
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects' import Api from '...' // worker Saga : 將在 USER_FETCH_REQUESTED action 被 dispatch 時調用 function* fetchUser(action) { try { // yield call([obj, obj.method], arg1, arg2, ...) // 如同 obj.method(arg1, arg2 ...) const user = yield call(Api.fetchUser, action.payload.userId); yield put({type: "USER_FETCH_SUCCEEDED", user: user}); } catch (e) { yield put({type: "USER_FETCH_FAILED", message: e.message}); } } /* 在每一個 `USER_FETCH_REQUESTED` action 被 dispatch 時調用 fetchUser 容許併發(譯註:即同時處理多個相同的 action) */ function* mySaga() { yield takeEvery("USER_FETCH_REQUESTED", fetchUser); } /* 也可使用 takeLatest 不容許併發,dispatch 一個 `USER_FETCH_REQUESTED` action 時, 若是在這以前已經有一個 `USER_FETCH_REQUESTED` action 在處理中, 那麼處理中的 action 會被取消,只會執行當前的 */ function* mySaga() { yield takeLatest("USER_FETCH_REQUESTED", fetchUser); // 非異步或而外操做不須要 直接actions // yield takeEvery(DELETE_LIST_SAGA, deleteList) } export default mySaga;
store.js
import { createStore, applyMiddleware } from 'redux' import createSagaMiddleware from 'redux-saga' import reducer from './reducers' import mySaga from './sagas' // create the saga middleware const sagaMiddleware = createSagaMiddleware() // mount it on the Store const store = createStore( reducer, applyMiddleware(sagaMiddleware) ) // then run the saga sagaMiddleware.run(mySaga) // render the application
當咱們須要 yield 一個包含 effects 的數組, generator 會被阻塞直到全部的 effects 都執行完畢,或者當一個 effect 被拒絕 (就像 Promise.all 的行爲)。
const [users, repos] = yield [ call(fetch, '/users'), call(fetch, '/repos') ]
immutable https://immutable-js.github.io/immutable-js/
Immutable 詳解及 React 中實踐: https://github.com/camsong/blog/issues/3
Immutable Data 就是一旦建立,就不能再被更改的數據。對 Immutable 對象的任何修改或添加刪除操做都會返回一個新的 Immutable 對象
immutable對象 正常使用相關遍歷方法(map)
immutable對象 不能直接經過下標訪問. 能夠經過轉化爲原始對象後訪問
const newList = list.toJS();
immutable對象 獲取長度 是==size==
reducer.js
import * as contants from './actionTypes' import { fromJS } from 'immutable' // immutable對象 const initialState = fromJS({ cn: 'yewq' }) export default (state = initialState, action) => { switch (action.type) { case contants.DEFAULT: // 嵌套的話 setIn(['cn', '..']) 修改多個值merge({...}) 根據原有值更新 updateIn return state.set('cn', action.payload) default: return state } }
組件中取值 store中{}都是immutable建立對象 api獲取的數據(對象類型)也用fromJS()包裹後存入store
import React from 'react' import { connect } from 'react-redux' import { setName, fetchName } from '../../store/name/actions' import './App.styl' function App({ name, setName, fetchName }) { return ( <div className="App"> <header className="App-header"> <h1 onClick={() => setName('test')}>{name}</h1> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ) } const mapStateToProps = (state, ownProps) => { return { //immutable取值 get('cn') 嵌套數據 getIn name: state.getIn(['user', 'cn']) } } export default connect( mapStateToProps, { setName, fetchName } )(App)
reducers的合併 藉助redux-immutable
import { combineReducers } from 'redux-immutable' import nameReducer from './name/reducer' const reducers = combineReducers({ user: nameReducer }) export default reducers
import { combineReducers } from 'redux-immutable' import name from './name/reducer' export default combineReducers({ name })
yarn add antd
yarn add react-app-rewired customize-cra babel-plugin-import
/* package.json */ "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test" },
根目錄新建 config-overrides.js
const { override, fixBabelImports } = require('customize-cra'); module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css', }), );
使用
import { Button } from 'antd';
FAQ:
yarn eject 以後 須要 yarn install一下
更換scripts後 若丟失react-scripts 從新安裝一下便可
安裝
yarn add react-router-dom
導航式
import React from "react"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; function Home() { return <h2>Home</h2>; } function About() { return <h2>About</h2>; } function Users() { return <h2>Users</h2>; } export default function App() { return ( <Router> <div> <nav> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> <li> <Link to="/users">Users</Link> </li> </ul> </nav> {/* A <Switch> looks through its children <Route>s and renders the first one that matches the current URL. */} <Switch> <Route path="/about"> <About /> </Route> <Route path="/users"> <Users /> </Route> <Route path="/"> <Home /> </Route> </Switch> </div> </Router> ); }
跳轉標籤
<link to='/'></link>
顯示標籤
<Route path='/'></Route>
嵌套路由放前面, 第二才放不匹配的
<Route path="/contact/:id"> <Contact /> </Route> <Route path="/contact"> <AllContacts /> </Route>
鉤子 useRouteMatch useParams return以前使用
useParams -> '/:id'
useLocation -> '?id=1'
app.js
<Route path='/nestedRouting' component={NestedRouting}></Route>
NestedRouting.js
import React, { Fragment } from 'react' import { Switch, Route, useRouteMatch, useParams, Link } from 'react-router-dom' function Topic() { let { topicId } = useParams() return <h3>Requested topic ID: {topicId}</h3> } // 嵌套路由 export default function NestedRouting() { let match = useRouteMatch() console.log(match) return ( <Fragment> <div> <div>NestedRouting</div> </div> <Switch> <Route path={`${match.path}/:topicId`}> <Topic /> </Route> <Route path={match.path}> <p> <Link to={`${match.url}/li1`}>1123</Link> </p> <p> <Link to={`${match.url}/li2`}>222222</Link> </p> </Route> </Switch> </Fragment> ) }
function Topic() { let { topicId } = useParams() let match = useRouteMatch() console.log(match) return topicId === 'back' ? ( <Redirect to={`/antd`}></Redirect> ) : ( <h3>Requested topic ID: {topicId}</h3> ) }
import { useHistory } from "react-router-dom"; function HomeButton() { let history = useHistory(); function handleClick() { history.push("/home"); } return ( <button type="button" onClick={handleClick}> Go home </button> ); }
<Route path='/' exact component={Home}></Route> <Route path='/login' exact component={Login}></Route>
常見建議是將您的應用劃分爲單獨的路由,並異步加載每一個路由。對於許多應用程序來講,這彷佛已經足夠好了-做爲用戶,單擊連接並等待頁面加載是網絡上的一種熟悉體驗。
react-loadable能夠作得更好。
api 介紹
const LoadableComponent = Loadable({ loader: () => import('./Bar'), loading: LoadingComponent, delay: 200, timeout: 10000, <!--render(loaded, props) {--> <!-- let Bar = loaded.Bar.default;--> <!-- let i18n = loaded.i18n;--> <!-- return <Bar {...props} i18n={i18n}/>;--> <!--},--> }); function LoadingComponent(props) { if (props.error) { // When the loader has errored return <div>Error! <button onClick={ props.retry }>Retry</button></div>; } else if (props.timedOut) { // When the loader has taken longer than the timeout return <div>Taking a long time... <button onClick={ props.retry }>Retry</button></div>; } else if (props.pastDelay) { // When the loader has taken longer than the delay return <div>Loading...</div>; } else { // When the loader has just started return null; } }
使用
import React from 'react' import Loadable from 'react-loadable' const LoadableComponent = Loadable({ loader: () => import('./組件'), loading() { return <div>正在加載</div> } }) export default () => <LoadableComponent />
若是路由傳參的話 須要withRouter包裹一下組件(使組件能訪問路由) 或者直接使用useParams
import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { DetailWrapper, Header, Content } from './style'; import { actionCreators } from './store'; class Detail extends PureComponent { render() { return ( <DetailWrapper> <Header>{this.props.title}</Header> <Content dangerouslySetInnerHTML={{__html: this.props.content}} /> </DetailWrapper> ) } componentDidMount() { this.props.getDetail(this.props.match.params.id); } } const mapState = (state) => ({ title: state.getIn(['detail', 'title']), content: state.getIn(['detail', 'content']) }); const mapDispatch = (dispatch) => ({ getDetail(id) { dispatch(actionCreators.getDetail(id)); } }); export default connect(mapState, mapDispatch)(withRouter(Detail));
app.js
import Detail from './pages/detail/loadable.js'; <Route path='/detail/:id' exact component={Detail}></Route>
yarn eject
webpack.config.js下 module->rules->oneOf 添
{ test: /\.styl$/, use: [ require.resolve('style-loader'), require.resolve('css-loader'), require.resolve('stylus-loader') ] },
Hook 是一些可讓你在函數組件裏「鉤入」 React state 及生命週期等特性的函數。
import React, { useState } from 'react'; function Example() { // 聲明一個叫 「count」 的 state 變量。 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
經過在函數組件裏調用它來給組件添加一些內部 state。React 會在重複渲染時保留這個 state。useState 會返回一對值:當前狀態和一個讓你更新它的函數,你能夠在事件處理函數中或其餘一些地方調用這個函數。
useEffect 就是一個 Effect Hook,給函數組件增長了操做反作用的能力。它跟 class 組件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具備相同的用途,只不過被合併成了一個 API。(咱們會在使用 Effect Hook 裏展現對比 useEffect 和這些方法的例子。)
==在 React 組件中有兩種常見反作用操做:須要清除的和不須要清除的。==
不須要清除的
function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); }
須要清除的 effect
以前,咱們研究瞭如何使用不須要清除的反作用,還有一些反作用是須要清除的。例如訂閱外部數據源。這種狀況下,清除工做是很是重要的,能夠防止引發內存泄露!如今讓咱們來比較一下如何用 Class 和 Hook 來實現。
爲何要在 effect 中返回一個函數? 這是 effect 可選的清除機制。每一個 effect 均可以返回一個清除函數。如此能夠將添加和移除訂閱的邏輯放在一塊兒。它們都屬於 effect 的一部分。
useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; });
==經過跳過 Effect 進行性能優化==
若是某些特定值在兩次重渲染之間沒有發生變化,你能夠通知 React 跳過對 effect 的調用,只要傳遞數組做爲 useEffect 的第二個可選參數便可:
useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // 僅在 count 更改時更新
添加eslint檢測
npm install eslint-plugin-react-hooks --save-dev
// 你的 ESLint 配置 { "plugins": [ // ... "react-hooks" ], "rules": { // ... "react-hooks/rules-of-hooks": "error", // 檢查 Hook 的規則 "react-hooks/exhaustive-deps": "warn" // 檢查 effect 的依賴 } }
有時候咱們會想要在組件之間重用一些狀態邏輯。目前爲止,有兩種主流方案來解決這個問題:高階組件和 render props。自定義 Hook
可讓你在不增長組件的狀況下達到一樣的目的。
一個叫 FriendStatus 的組件,它經過調用 useState 和 useEffect 的 Hook 來訂閱一個好友的在線狀態。假設咱們想在另外一個組件裏重用這個訂閱邏輯。
import React, { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
如今咱們能夠在兩個組件中使用它:
function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; } function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }
除此以外,還有一些使用頻率較低的可是頗有用的 Hook。好比,useContext 讓你不使用組件嵌套就能夠訂閱 React 的 Context。
function Example() { const locale = useContext(LocaleContext); const theme = useContext(ThemeContext); // ... }
initialArg: 初始 state | init: 惰性初始化-> 初始 state 將被設置爲 init(initialArg)
const [state, dispatch] = useReducer(reducer, initialArg, init);
在某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較複雜且包含多個子值,或者下一個 state 依賴於以前的 state 等。而且,使用 useReducer 還能給那些會觸發深更新的組件作性能優化,由於你能夠向子組件傳遞 dispatch 而不是回調函數 。
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
惰性初始化
你能夠選擇惰性地建立初始 state。爲此,須要將 init 函數做爲 useReducer 的第三個參數傳入,這樣初始 state 將被設置爲 init(initialArg)。
這麼作能夠將用於計算 state 的邏輯提取到 reducer 外部,這也爲未來對重置 state 的 action 作處理提供了便利:
function init(initialCount) { return {count: initialCount}; } function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; case 'reset': return init(action.payload); default: throw new Error(); } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
把內聯回調函數及依賴項數組做爲參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時纔會更新。當你把回調函數傳遞給通過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將很是有用。
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
function Counter() { const [count, setCount] = useState(0); const prevCount = usePrevious(count); return <h1>Now: {count}, before: {prevCount}</h1>; } function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; }
style.類名
import style from './index.module.css'; return ( <div className={style.face_container}> <div className={style.login_bt}> <p> 友情提示: 由於facebook api一段時間內訪問有流量限制, 小夥伴工做時間儘可能錯開哈~ 使用時能夠在羣通報一聲. </p> <Button type="primary" loading={this.state.loginLoading} onClick={this.resetLogin} > 若是訪問令牌失效了,才點擊從新登陸 </Button> <Button type="primary" loading={this.state.adaccountLoading} onClick={this.getNewAdaccounts} > 若是廣告帳戶有新添,才點擊從新獲取廣告帳戶 </Button> </div> <Ads options={this.state.options} /> </div> ); }
建立
npx create-react-app my-app --typescript
或者添加 TypeScript到現有項目中
yarn add --dev typescript
在配置編譯器以前,讓咱們將 tsc 添加到 package.json 中的 「scripts」 部分:
{ // ... "scripts": { "build": "tsc", // ... }, // ... }
函數組件
hello.tsx
import React from 'react' interface HelloProps { name: string age?: number } const Hello: React.FC<HelloProps> = ({ name }) => { return <>hello, {name}</> } export default Hello
類組件
import * as React from 'react'; export interface MouseProviderProps { render: (state: MouseProviderState) => React.ReactNode; } interface MouseProviderState { readonly x: number; readonly y: number; } export class MouseProvider extends React.Component<MouseProviderProps, MouseProviderState> { readonly state: MouseProviderState = { x: 0, y: 0 }; handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => { this.setState({ x: event.clientX, y: event.clientY, }); }; render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {/* Instead of providing a static representation of what <Mouse> renders, use the `render` prop to dynamically determine what to render. */} {this.props.render(this.state)} </div> ); } }
添加泛型T 使用
import * as React from 'react'; export interface GenericListProps<T> { items: T[]; itemRenderer: (item: T) => JSX.Element; } export class GenericList<T> extends React.Component<GenericListProps<T>, {}> { render() { const { items, itemRenderer } = this.props; return ( <div> {items.map(itemRenderer)} </div> ); } }
const scrollStyle = (): React.CSSProperties => ({ position: 'fixed', top: contentTop + 'px' }) <div className={style.scroll_container} style={contentTop > 0 ? scrollStyle() : undefined} ></div>
目錄結構
├─src ├─assets ├─components │ └─App ├─pages └─store └─name