react 我的學習過程

安裝

npx create-react-app your-app

格式化

Prettier - Code formattercss

或者右下角語言選JavascriptReact或者TypescriptReacthtml

vscode開發相關配置

https://blog.csdn.net/weixin_40461281/article/details/79964659node

jsx語法

{  }
布爾類型、Null 以及 Undefined 將會忽略

值得注意的是有一些 「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 & 生命週期

在這裏插入圖片描述

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
}));

列表 & Key

在 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>
    );
  }
}

狀態提高 (props)

在 React 應用中,任何可變數據應當只有一個相對應的惟一「數據源」。一般,state 都是首先添加到須要渲染數據的組件中去。而後,若是其餘組件也須要這個 state,那麼你能夠將它提高至這些組件的最近共同父組件中。你應當依靠自上而下的數據流,而不是嘗試在不一樣組件間同步 state。

懶加載組件 React.lazy 須要配合Suspense使用

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>
);

錯誤邊界(Error Boundaries)

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

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 值

更新 Context

  1. 動態 Context ---> 修改 傳入的value(state)

頂部組件

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
  1. 在嵌套組件中更新 Context

你能夠經過 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

ref 獲取 Dom節點

回調函數裏參數獲取是Dom節點

  1. 回調函數
<input type="number" name="num" id=""  value={this.state.num} onChange={this.setVal}  ref={(input) => { this.input = input }} />
  1. 用React.createRef()接收
this.file = React.createRef()

<input type="file" ref={this.file}/>

在 JSX 類型中使用點語法

當你在一個模塊中導出許多 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

  1. shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }
  1. 繼承React.PureComponent state是immutable對象優先
class CounterButton extends React.PureComponent {

    
}

react-transition-group

CSSTransition

appear 入場動畫

unmountOnExit 動畫結束關閉 none

鉤子函數 onEnter onEntering onEntered (onExit)

HOC (使用 HOC 解決橫切關注點問題 以前是mixins )

// 此函數接收一個組件...
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} />;
    }
  };
}
注意事項
  • 不要在 render 方法中使用 HOC
  • 約定:將不相關的 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 Props

術語 「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>
    );
  }
}

PropTypes

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);

Redux

在這裏插入圖片描述

安裝

yarn add redux

調試工具 redux-devtools

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
subscribe, dispatch, getState的使用
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>
    )
  }
}
reducer 只是一個接收 state 和 action,並返回新的 state 的函數

對於大的應用來講,不大可能僅僅只寫一個這樣的函數,因此咱們編寫不少小函數來分別管理 state 的一部分:

action

把action.type 抽離出來 actionTypes.js

書寫錯誤時會有提示.

// 報錯提示
const ADD_LIST = 'ADD_LIST'
actionCreator

生成 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')

reducer

combineReducers
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

Store 有如下職責:

  • 維持應用的 state;
  • 提供 getState() 方法獲取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 經過 subscribe(listener) 註冊監聽器;
  • 經過 subscribe(listener) 返回的函數註銷監聽器。
createStore

createStore() 的第二個參數是可選的, 用於設置 state 初始狀態 使用服務器state時

react-redux

使用 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)
傳入 Store Provider包裹
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')
);

分容器組件 和 展現組件(函數組件) 其它組件(組件的視圖和邏輯混合)

在這裏插入圖片描述

<容器組件>
        <展現組件/>
    <容器組件/>
    <其它組件/>

異步請求處理的中間件 redux-thunk / redux-saga

redux-thunk

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)
    })
}

redux-saga

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 性能優化

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
redux-immutable 統一格式
import { combineReducers } from 'redux-immutable'
import name from './name/reducer'

export default combineReducers({
  name
})

antd

安裝

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 從新安裝一下便可


react-router

安裝

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>

嵌套路由

嵌套路由放前面, 第二才放不匹配的

的特定性更高(一般更長)放在不那麼特定的paths 以前。

<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>
  )
}

Redirect

function Topic() {
  let { topicId } = useParams()
  let match = useRouteMatch()
  console.log(match)
  return topicId === 'back' ? (
    <Redirect to={`/antd`}></Redirect>
  ) : (
    <h3>Requested topic ID: {topicId}</h3>
  )
}

鉤子 useHistory useLocation

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>
  );
}

exact 路徑徹底匹配時纔會顯示

<Route path='/' exact component={Home}></Route>
<Route path='/login' exact component={Login}></Route>

react-loadable

在這裏插入圖片描述

常見建議是將您的應用劃分爲單獨的路由,並異步加載每一個路由。對於許多應用程序來講,這彷佛已經足夠好了-做爲用戶,單擊連接並等待頁面加載是網絡上的一種熟悉體驗。

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>

react 引入 stylus

yarn eject

webpack.config.js下 module->rules->oneOf 添

{
  test: /\.styl$/,
  use: [
    require.resolve('style-loader'),
    require.resolve('css-loader'),
    require.resolve('stylus-loader')
  ]
},

Hook (獨立的)

Hook 是一些可讓你在函數組件裏「鉤入」 React state 及生命週期等特性的函數。

State Hook
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 會返回一對值:當前狀態和一個讓你更新它的函數,你能夠在事件處理函數中或其餘一些地方調用這個函數。

Effect Hook 能夠用多個 Effect 實現關注點分離

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 更改時更新
若是想執行只運行一次的 effect(僅在組件掛載和卸載時執行),能夠傳遞一個空數組([])做爲第二個參數。這就告訴 React 你的 effect 不依賴於 props 或 state 中的任何值,因此它永遠都不須要重複執行。這並不屬於特殊狀況 —— 它依然遵循輸入數組的工做方式。
Hook 使用規則
  • 只能在函數最外層調用 Hook。不要在循環、條件判斷或者子函數中調用。
  • 只能在 React 的函數組件中調用 Hook。不要在其餘 JavaScript 函數中調用。(還有一個地方能夠調用 Hook —— 就是自定義的 Hook 中,咱們稍後會學習到。)

添加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 的依賴
  }
}
自定義 Hook

有時候咱們會想要在組件之間重用一些狀態邏輯。目前爲止,有兩種主流方案來解決這個問題:高階組件和 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

除此以外,還有一些使用頻率較低的可是頗有用的 Hook。好比,useContext 讓你不使用組件嵌套就能夠訂閱 React 的 Context。

function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}
useContext(MyContext) 至關於 class 組件中的 static contextType = MyContext 或者 <MyContext.Consumer>。
useContext(MyContext) 只是讓你可以讀取 context 的值以及訂閱 context 的變化。你仍然須要在上層組件樹中使用 <MyContext.Provider> 來爲下層組件提供 context。
useReducer 可讓你經過 reducer 來管理組件本地的複雜 state。

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

把內聯回調函數及依賴項數組做爲參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時纔會更新。當你把回調函數傳遞給通過優化的並使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將很是有用。

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
usePrevious
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;
}

CSS Module

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>
    );
  }

typescript

建立

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>
    );
  }
}

使用style

const scrollStyle = (): React.CSSProperties => ({
    position: 'fixed',
    top: contentTop + 'px'
  })

<div
  className={style.scroll_container}
  style={contentTop > 0 ? scrollStyle() : undefined}
></div>

我的項目模板 react-basic

branch
  • master
  • immutable 引入 immutable + react-loadable

目錄結構

├─src
    ├─assets
    ├─components
    │  └─App
    ├─pages
    └─store
        └─name
相關文章
相關標籤/搜索