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