本文是本身最近再學習React的總結和記錄。盡力把每一個示例代碼都寫了出來,盡力寫的通俗易懂一些,也算是鍛鍊本身的講述能力,聽說給別人講的話學的會更快,因此寫下此文。html
若有錯誤或者其餘,還望各位批評指正react
還沒整理徹底,趁着沒開學,我會抓緊時間學習和更新es6
傳送門 好像沒用,本地均可以.........npm
更新部分函數組件的用法,提供基本思路和例子編程
在JS裏面寫的一些標籤稱爲JSX語法redux
在返回的時候不須要加引號api
import React from 'react';
import ReactDOM from 'react-dom';
import logo from './logo.svg';
// 基本
const jsx = <h1>hello react</h1>
// 變量
const name = '123456'
const jsxName = <h1>{ name }</h1>
// 函數
const user = { firstName: 'tom', lastName: 'jerry' };
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const jsxFunction = <h2>{ formatName(user) }</h2>
// 對象
const greet = <p>hello, Jerry</p>
const jsxObject = <h2>{ greet }</h2>
// 三元表達式
const showTitle = true;
const title = showTitle ? <h2>{ name }</h2> : null;
const jsxOthers = (<div>{
/* 條件語句句 */
} { title }
</div>);
// 循環
const arr = [1, 2, 3].map(num => <li key={ num }>{ num } </li>)
const jsxArray = (<div> {/* 數組 */ }
<ul>{ arr }</ul>
</div>)
// 注意
const jsxAtt = (<div>
{
/* 屬性:靜態值⽤用雙引號,動態值⽤用花括號;class、for等 要特殊處理理。
* 像class須要寫成className,for須要寫成htmlFor,而且屬性名須要採用駝峯命名法
* */
}
<img src={ logo } style={ { width: 100 } } className="img"/></div>)
// 函數傳參
function greeting(name) {
if (name) {
return <h1>Hello, {name}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
let name2 = '測試';
const element = greeting(name2);
// 循環
let names = ['張三','李四','王五'];
let elements = [];
for(let i=0;i<names.length;i++){
elements.push(<li>{names[i]}</li>);
}
export { jsx, jsxName, jsxFunction, jsxObject, jsxOthers, jsxArray,jsxAtt,element,elements }
複製代碼
能夠將UI切分紅一些獨立的、可複用的部件,數組
class組件一般擁有狀態和生命週期,繼承於Component,實現 render方法瀏覽器
import React, { Component } from 'react';
class Home extends Component {
render() {
return <h1>Hello, Class Components</h1>;
}
}
複製代碼
import React, { Component } from 'react';
class State extends Component {
// 使用state屬性維護狀態,在構造函數中初始化狀態
constructor(props) {
super(props);
this.state = { date: new Date(), number: 0 };
// this.changeNumber = this.changeNumber.bind(this)
}
// 組件掛載時啓動定時器每秒更新狀態
componentDidMount() {
this.timerID = setInterval(() => {
// 使⽤用setState方法更新狀態
this.setState({ date: new Date() });
}, 1000);
}
// 組件卸載時中止定時器
componentWillUnmount() {
clearInterval(this.timerID);
}
// 這裏不綁定this 會報錯 兩種方法 第一種 用箭頭函數。第二種在constructor進行綁定聲明
// setState是異步執行的。執行這個結果是 log 是2不是3 證實是異步執行
changeNumber = () => {
this.setState({ number: this.state.number + 1 } )
this.setState({ number: this.state.number + 2 } )
// 這裏不能進行直接修改
console.log(this.state.number)
}
// 同步改變
changeNumber2 = ()=> {
this.setState((nextState)=>{ return { number: nextState.number + 1 } } )
this.setState((nextState)=>{ return { number: nextState.number + 2 } } )
console.log(this.state.number)
// 使用定時器
// setTimeout(() => { this.setState((nextState)=>{ return { number: nextState.number + 1 } } ); console.log(this.state.counter); }, 0);
// 原生事件中修改狀態
// componentDidMount(){ document.body.addEventListener('click', this.setState((nextState)=>{ return { number: nextState.number + 1 } } ), false) }
}
render() {
return (
<div> { this.state.date.toLocaleTimeString() } <p>{ this.state.number }</p> <p>setState是異步執行的</p> <button onClick={this.changeNumber}>異步改變number</button> <button onClick={this.changeNumber2}>同步改變number</button> </div>
)
}
}
複製代碼
在constructor
設置狀態。使用setState
來改變狀態。示例如上緩存
注意,setState是 異步的!!!!
如何證實?
見示例代碼中changeNumber
的描述和實現
常見的有三種方法:
上述三種方法在示例代碼中均可以找到。
通常有兩種方法
constructor
裏面用bind
方法綁定。例如this.changeNumber = this.changeNumber.bind(this)
changeNumber = () => { // 執行代碼 }
。(**緣由解釋:**箭頭函數默認指向定義它時,所處上下文的對象的this指向)import React from "react";
import User from "./pages/User";
function App() {
return (
<div> <User /> </div>
);
}
export default App;
複製代碼
後文的 react HOOKs
會作詳細解釋
適用於父子組件通信
// index.js
ReactDOM.render(<App title="測試代碼" />, document.querySelector('#root')); // App.js <h2>{this.props.title}</h2> // 輸出爲 測試代碼 複製代碼
官網原話 :組件不管是使用函數聲明仍是經過 class 聲明,都決不能修改自身的 props
官方解釋: 一般,多個組件須要反映相同的變化數據,這時咱們建議將共享狀態提高到最近的共同父組件中去。
實例代碼
import React from 'react'
class Child_1 extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div> <h1>{this.props.value+'child 1'}</h1> </div>
)
}
}
class Child_2 extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div> <h1>{this.props.value+'child 2'}</h1> </div>
)
}
}
class Parent extends React.Component {
constructor(props){
super(props)
this.state = {
txt:"我是父組件"
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(e){
this.setState({
txt:e.target.value
})
}
render(){
return (
<div>
<input type="text" value={this.state.txt} onChange={this.handleChange}/>
<p>{this.state.txt}</p>
<Child_1 value={this.state.txt}/>
<Child_2 value={this.state.txt}/>
</div>
)
}
}
export default Parent
複製代碼
跨層級組件之間通訊
// ContextFather.js
import React from 'react';
import ContextChild from './child';
const Context = React.createContext()
const Provider = Context.Provider
const Consumer = Context.Consumer
const data = {
name: '我是father數據',
}
function ContextApp() {
return (
<div> <Provider value={ data }> <Consumer> { value => <ContextChild {...value}/> } </Consumer> </Provider> </div> ) } export default ContextApp // child.js import React, { Component } from 'react'; class ContextChild extends Component { constructor(props) { super(props); } render() { console.log(this.props) return (<h1>hello {this.props.name}</h1>); } } export default ContextChild 複製代碼
這樣能夠獲取到傳遞過來的name
值
// Context.js
import React from 'react';
const Context = React.createContext()
const Provider = Context.Provider
const Consumer = Context.Consumer
export {Provider,Consumer}
// ContextFather
import React from 'react';
import ContextChild from './child';
import {Consumer,Provider} from './Context';
const data = {
name: '我是father數據',
}
function ContextApp() {
return (
<div> <Provider value={ data }> // 這個層級不須要就不用寫 Consumer <ContextChild/> </Provider> </div>
)
}
export default ContextApp
// child.js
import React, { Component } from 'react';
import ContextChild2 from './child2';
import {Consumer} from './Context';
class ContextChild extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div> <h1>我是第一層</h1> // 這裏獲取的是一個空值,沒有數據 <h2>{this.props.name}</h2> // 這個層級須要就吧數據傳遞過去 <Consumer> { value => <ContextChild2 {...value}/> } </Consumer> </div> ) } } export default ContextChild // child2.js import React, { Component } from 'react'; class ContextChild2 extends Component { constructor(props) { super(props); } render() { console.log(this.props) console.log('我是第二層的') return ( <div> <h1>我是第二層的</h1> <h1>{this.props.name}</h1> </div> ); } } export default ContextChild2 複製代碼
Context.js
裏面的語句。則會獲取不到數據。不一樣的provider提供的值不一致Context
被歸類爲高級部分(Advanced),屬於React的高級API,但官方並不建議在穩定版的App中使用Context。不過,這並不是意味着咱們不須要關注Context。事實上,不少優秀 的React組件都經過Context來完成⾃本身的功能,好比react-redux沒學過Vue的能夠理解爲全局狀態管理,即你在哪裏都能訪問的到(我我的是這麼理解的)
解釋: reducer 就是一個純函數,接收舊的 state
和 action
,返回新的 state
本例子來源於MDN
const array1 = [1, 2, 3, 4];
const reducer = (total, currentValue) => total + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
複製代碼
compose
就是執行一系列的任務(函數)
如何輸出1 2 3
function f1() { console.log("1"); }
function f2() { console.log("2"); }
function f3() { console.log("3"); }
複製代碼
第一種
f1();f2();f3();
第二種
f3(f2(f1()))
第三種
function compose(...funcs) {
// funcs 是一個函數列表
if (funcs.length === 0) {
console.log('empty');
} else if (funcs.length === 1) {
return funcs[0];
} else {
return funcs.reduce(
(left, right) => (...args) => {
// console.log(args)
// console.log(right)
// console.log(left)
right(left(...args)) // 至關於 f3(f2(f1()))
}
);
}
}
compose(f1,f2,f3)()
複製代碼
store
來存儲數據store
裏reducer
初始化state
並定義state
修改規則dispatch
一個action
來提交對數據的修改action
提交到reducer
函數里,根據傳入的action
的type
,返回新的 state
實例代碼
// 建立store store/ReduxStore
import { createStore } from 'redux';
// 建立數據並指定行爲
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'add':
return state + 1
case 'minus':
return state - 1
default:
return state
}
}
// 建立一個store
const store = createStore(counterReducer)
export default store
複製代碼
// ReduxPage
import React, { Component } from 'react';
import store from '../store/ReduxStore';
export default class ReduxPage extends Component {
componentDidMount() { // 掛在以後
store.subscribe(() => { // 執行訂閱
console.log('subscribe');
this.forceUpdate(); // 從新render
//this.setState({}); // 這個也能夠,內部方法也是forceUpdate
});
}
// 執行行爲
add = () => {
// 派發action
store.dispatch({ type: 'add' });
};
minus = () => {
store.dispatch({ type: 'minus' });
};
stayStatic = () => {
store.dispatch({ type: 'others' });
};
render() {
console.log('store', store);
return (
<div> <h1>ReduxPage</h1> <p>獲取store數據</p> <p>{ store.getState() }</p> <button onClick={ this.add }>+</button> <button onClick={ this.minus }>-</button> <button onClick={ this.stayStatic }>+-</button> </div>
);
}
}
複製代碼
若是點擊後 數據沒有更新。則是訂閱狀態沒有改變(也就是沒有render
)
createStore
建立store
reducer
初始化、修改狀態函數getState
獲取狀態值dispatch
提交更新subscribe
變更訂閱以React
的方式寫來Redux
提供了兩個api
Provider
爲後代組件提供store
(相似Context
)connect
爲組件提供數據和變動⽅方法原來的只能夠同步執行,安裝以後,能夠進行異步操做
只能夠再開發環境下使用。用來輸出各個action
你們會發現其實仍是有 redux
的引入,說明react-redux
也是基於Redux
import { createStore, applyMiddleware,combineReducers } from 'redux';
import logger from 'import React, { Component } from 'react';
import { connect } from 'react-redux';
class ReactReduxPage extends Component {
render() {
const { num, add, minus,asyAdd } = this.props;
// console.log(this.props) // 本來這裏面什麼都沒有,再進行connect映射後出現了
return (
<div>
<h1>ReactReduxPage</h1>
<p>{ num }</p>
<button onClick={ add }>+</button>
<button onClick={ minus }>-</button>
<button onClick={ asyAdd }>asyAdd</button>
</div>
);
}
}
const mapStateToProps = state => {
console.log(state)
return { num: state };
// return { num: state.counter };// 加了combineReducers 以後須要這麼作 由於是一個對象
};
const mapDispatchToProps = {
add: () => {
return { type: 'add' };
},
minus: () => {
return { type: 'minus' };
},
asyAdd: ()=> dispatch =>{
setTimeout(()=>{
dispatch ({type: 'add'})
},1000)
}
};
export default connect(
mapStateToProps,//狀態映射 mapStateToProps
mapDispatchToProps,//派發事件映射
)(ReactReduxPage)
';
import thunk from 'redux-thunk';
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'add':
return state + 1
case 'minus':
return state - 1
default:
return state
}
}
// const store = createStore(counterReducer)
// const store = createStore(combineReducers({counter: counterReducer}), applyMiddleware(logger, thunk))
const store = createStore(counterReducer, applyMiddleware(logger, thunk))
export default store
複製代碼
applyMiddleware
提供擴展中間件(有順序,這個事先執行logger,在執行thunk)
combineReducers
命名空間,當你有多個store的時候,使用這個,就能夠用對象的方式訪問各個store
import React, { Component } from 'react';
import { connect } from 'react-redux';
class ReactReduxPage extends Component {
render() {
const { num, add, minus,asyAdd } = this.props;
// console.log(this.props) // 本來這裏面什麼都沒有,再進行connect映射後出現了
return (
<div> <h1>ReactReduxPage</h1> <p>{ num }</p> <button onClick={ add }>+</button> <button onClick={ minus }>-</button> <button onClick={ asyAdd }>asyAdd</button> </div>
);
}
}
const mapStateToProps = state => {
console.log(state)
return { num: state };
// return { num: state.counter };// 加了combineReducers 以後須要這麼作 由於是一個對象
};
const mapDispatchToProps = {
add: () => {
return { type: 'add' };
},
minus: () => {
return { type: 'minus' };
},
asyAdd: ()=> dispatch =>{
setTimeout(()=>{
dispatch ({type: 'add'})
},1000)
}
};
export default connect(
mapStateToProps,//狀態映射 mapStateToProps
mapDispatchToProps,//派發事件映射
)(ReactReduxPage)
複製代碼
固然,若是嫌棄太亂,能夠單獨抽離出來放。最後導入進來就能夠
這個也是官方推薦的作法: 咱們推薦使用組合而非繼承來實現組件間的代碼重用。
學過Vue的同窗,我我的理解爲相似於Vue的插槽
// Layout.js
import React, { Component } from 'react';
import TabBar from '../components/TabBar';
class Layout extends Component{
constructor(props) {
super(props);
}
componentDidMount() {
const {title = '首頁'} = this.props
document.title = title
}
// 兼容具名和不具名 compatible
compatibleNamed(children) {
let doc = []
if (children.$$typeof) {
doc.push(children)
} else {
for (let item in children){
doc.push(children[item])
}
}
return doc
}
render() {
console.log(this.props)
return (
<div> <h1>我是佈局頁面</h1> {/* 不具名的使用*/} {/*{*/} {/* this.props.children*/} {/*}*/} {/* 這個是具名的使用*/} { this.props.children.btn } <h2>兼容事後</h2> {this.compatibleNamed(this.props.children).map((item,index)=>{ return ( <div key={index}> {item} </div> ) })} { this.props.showTab && <TabBar/> } </div>
)
}
}
export default Layout
// Home.js
import React, { Component } from 'react';
import Layout from '../ComponentCombination/Layout';
class Home extends Component {
render() {
return (
<DisNamed/>
)
}
}
function Named(props) {
return (
<Layout showTab={ false } title='商城首頁'> <h1>我是首頁</h1> </Layout>
)
}
function DisNamed() {
return (
<Layout showTab={ false } title='商城首頁'> { { btn: <button>我是按鈕</button> } } </Layout>
)
}
export default Home
// TabBar.js
import React, { Component } from 'react';
class TabBar extends Component{
render() {
return (
<div> <h1>我是TabBar</h1> </div>
)
}
}
export default TabBar
複製代碼
上面例子容易混淆,寫一個簡單點的
import React, { Component } from 'react';
import TabBar from '../components/TabBar';
class Layout extends Component{
constructor(props) {
super(props);
}
render() {
console.log(this.props)
return (
<div> <h1>我是佈局頁面</h1> {/* 不具名的使用*/} { this.props.children } { this.props.showTab && <TabBar/> } </div>
)
}
}
export default Layout
複製代碼
this.props.children
所展現的是兩個Layout
之間的內容
例如
<Layout>
<h1>hello</h1>
</Layout>
複製代碼
this.props.children
展現的就是<h1>hello</h1>
//Layout
import React, { Component } from 'react';
import TabBar from '../components/TabBar';
class Layout extends Component{
constructor(props) {
super(props);
}
render() {
console.log(this.props)
return (
<div> <h1>我是佈局頁面</h1> {/* 這個是具名的使用*/} { this.props.children.btn } { this.props.showTab && <TabBar/> } </div>
)
}
}
export default Layout
// Home.js
import React, { Component } from 'react';
import Layout from '../ComponentCombination/Layout';
class Home extends Component {
render() {
return (
<Named/>
)
}
}
function DisNamed() {
return (
<Layout showTab={ false } title='首頁'> { { btn: <button>我是按鈕</button> } } </Layout>
)
}
export default Home
複製代碼
在傳遞具名組件的時候,須要用雙{{}}
包裹。使用的就當屬性使用就能夠了
具名 和 不具名 的差別在於傳遞過來的時候this.props.children
是否含有$$typeof
屬性。若是不含有,則是具名。反之,含有則是不具名
如何兼容?
知道了二者差別。就從差別下手
compatibleNamed(children) {
let doc = []
if (children.$$typeof) { // 若是不是具名 直接放到數組裏
doc.push(children)
} else {
for (let item in children){ // 若是是具名,則便利children。把裏面的內容放進數組裏
doc.push(children[item])
}
}
return doc // 返回的是一個數組
}
// 使用
render(){
return (
{this.compatibleNamed(this.props.children).map((item,index)=>{
return (
<div key={index}> {item} </div>
)
})}
}
複製代碼
高階組件是一個工廠函數,它接收一個組件並返回另外一個組件。
簡單例子
function Child() {
return (
<div> child </div>
)
}
// Cmp是組件 props 是組件的props
const foo = Cmp => props =>{
return (
<div style={{boder: '1px solid #ccc'}}> <Cmp {...props}/> </div> ) } function APP() { const Foo = foo(Child) return ( <div> <Foo/> </div> ) } 複製代碼
建立 高階組件
import { Consumer } from './Context';
import React from 'react';
//Cum 是組件 props是組件傳遞的props
const HOC = Cum => props => {
return (
<Consumer> { value => <Cum { ...props } { ...value }/> } </Consumer> ) } export default HOC 複製代碼
原來的 Context寫法
import React, { Component } from 'react';
import ContextChild2 from './child2';
import HOC from './Hoc Hooks';
import {Consumer} from './Context';
class ContextChild extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div> <h1>我是第一層</h1> <h2>{this.props.name}</h2> <Consumer> { value => <ContextChild2 {...value}/> } </Consumer> </div> ) } } export default ContextChild 複製代碼
改用HOC(高階組件)以後
// child
import React, { Component } from 'react';
import ContextChild2 from './child2';
import HOC from './Hoc Hooks';
class ContextChild extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div> <h1>我是第一層</h1> <h2>{this.props.name}</h2> {/*直接調用*/} <ContextChild2/> </div>
)
}
}
export default HOC(ContextChild)
// child2.js
import React, { Component } from 'react';
import HOC from './Hoc Hooks';
class ContextChild2 extends Component {
constructor(props) {
super(props);
}
render() {
console.log(this.props)
console.log('我是第二層的')
return (
<div> <h1>我是第二層的</h1> <h1>{this.props.name}</h1> </div>
);
}
}
export default HOC(ContextChild2)
複製代碼
本來須要寫Consumer
如今只須要一個函數就解決了。很是方便
可能仍是會有點迷瞪。我在詳細的說一次
import { Consumer } from './Context';
import React from 'react';
const HOC = Cum => props => {
return (
<Consumer> { value => <Cum { ...props } { ...value }/> } </Consumer> ) } export default HOC 複製代碼
Cum
是組件,props
是組件的所攜帶的參數,value
是provider
所分發的數據.
類比一下下面的.就是把組件看成了參數,經過傳入一個組件,返回另外一個新的組件
<Consumer>
{
value => <ContextChild {...value}/> } </Consumer>
複製代碼
// ContextFather.js
import React from 'react';
import ContextChild from './child';
import {Consumer,Provider} from './Context';
const data = {
name: '我是father數據',
}
function ContextApp() {
return (
<div> <Provider value={ data }> {/*<Consumer>*/} {/* {*/} {/* value => <ContextChild {...value}/>*/} {/* }*/} {/*</Consumer>*/} <ContextChild/> </Provider> </div> ) } export default ContextApp // child.js import React, { Component } from 'react'; import ContextChild2 from './child2'; import HOC from './Hoc Hooks'; class ContextChild extends Component { constructor(props) { super(props); } render() { return ( <div> <h1>我是第一層</h1> <h2>{this.props.name}</h2> <ContextChild2/> </div> ) } } export default HOC(ContextChild) // child2.js import React, { Component } from 'react'; import HOC from './Hoc Hooks'; class ContextChild2 extends Component { constructor(props) { super(props); } render() { console.log(this.props) console.log('我是第二層的') return ( <div> <h1>我是第二層的</h1> <h1>{this.props.name}</h1> </div> ); } } export default HOC(ContextChild2) 複製代碼
react-router包含3個庫,react-router、react-router-dom和react-router-native。
react-router提供最 基本的路由功能,實際使用的時候咱們不會直接安裝react-router,而是根據應用運行行的環境選擇安裝
react-router-dom(在瀏覽器器中使⽤用)
react-router-native(在rn中使用)。
react-router-dom和 react-router-native都依賴react-router,因此在安裝時,react-router也會⾃自動安裝
npm install --save react-router-dom
經常使用 BrowserRouter 、連接-Link、路由-Route、獨佔路由Switch、重 定向路由-Redirect
// RouterPage
import React, { Component } from 'react';
import { BrowserRouter, Link, Route,Switch } from 'react-router-dom';
import HomePage from './HomePage';
import UserPage from './UserPage';
import SearchPage from './Search';
import Login from './Login';
export default class RouterPage extends Component {
render() {
return (
<div>
<h1>RouterPage</h1>
<BrowserRouter>
<nav>
<Link to="/">首頁 </Link>
<Link to="/user">用戶中心 </Link>
<Link to="/login">登錄 </Link>
</nav>
{/* 根路路由要添加exact,實現精確匹配 不加這個可能會出現屢次渲染 */ }
<Switch>
{/*匹配到以後就不在繼續往下匹配 Switch做用*/}
<Route exact path="/" component={ HomePage }/>
{/*<Route path="/user" component={ UserPage }/>*/}
<Route path="/login" component={ Login }/>
{/*404 頁面 必定要放到最後*/}
<Route component={() => <h1>404</h1>} />
</Switch>
</BrowserRouter>
</div>
);
}
}
複製代碼
// RouterPage
<Link to="/search/123">詳情 </Link>
<Route path="/search/:id" component={SearchPage} />
// Search
import React from 'react';
function SearchPage(props) {
console.log(props) // 有三個 match history location
return(
<div>
<p>經過這樣獲取到傳遞的參數</p>
SearchPage{props.match.params.id}
</div>
)
}
export default SearchPage
複製代碼
案例:好比你須要在登錄以後才能進入到用戶頁面,這時候就須要用路由守衛。
熟悉Vue的同窗應該能知道我想要表達的意思
//RouterPage
...
import RouterGuard from './RoutingGuard';
...
<Link to="/user">用戶中心 </Link>
<Link to="/login">登錄 </Link>
<RouterGuard path ='/user' isLogin={true} Cmp={UserPage}/>
...
複製代碼
// RouterGuard
import React from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';
function RouterGuard(props) {
const { Cmp, path, isLogin, ...rest } = props
console.log(rest) // 一些剩下的參數
return (
<Route
{ ...rest }
render={ prop =>{
console.log(prop) // 一些路由的參數
return isLogin ? (<Cmp { ...prop } />) :
(<Redirect to={ { pathname: '/login', redirect:props.location.pathname} }/>
)} }
>
</Route>
)
}
export default connect(
state => {
return {
isLogin: state.user.isLogin,
}
},
)(RouterGuard)
複製代碼
這裏面主要是用了渲染函數 render
, 還有一個三元表達式
// LoginStore 記得用provider 包裹APP
import { createStore, applyMiddleware, combineReducers } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
const initialLogin = { isLogin: false, name: null };
const userInfo = (state = {...initialLogin}, action) => {
switch (action.type) {
case 'getUser':
return { ...state,isLogin: false }
case 'LoginSuccess':
return { ...state,isLogin: true,name: '阿琛' }
case 'LoginFail':
return { ...initialLogin }
default:
return state
}
}
const store = createStore(
combineReducers({
user: userInfo
}),
applyMiddleware(logger, thunk))
export default store
// Login
import React from 'react';
import { connect } from 'react-redux';
import {Redirect} from 'react-router-dom'
function Login(props) {
const {location,login,isLogin} = props
console.log(props)
const redirect = location.redirect?location.redirect:'/'
if (isLogin){
return <Redirect to={redirect} />; } return ( <div> <h1>LoginPage</h1> <button onClick={login}>登錄</button> </div> ) } export default connect(state => ({ isLogin: state.user.isLogin, }), { login: () => { return { type: 'LoginSuccess' }; }, }, )(Login) 複製代碼
前面介紹了好多了,接下來介紹一些尚未介紹的
Redux-saga是Redux的一箇中間件,主要集中處理react架構中的異步處理工做,被定義爲generator(ES6)的形式,採用監聽的形式進行工做。
做用和react-thunk差很少。都是爲了解決異步
Generator 函數是 ES6 提供的一種異步編程解決方案,語法行爲與傳統函 數徹底不同,詳細參考參考阮一峯老師講解
npm install --save redux-saga
也是一個登錄功能
// RouterPage 加上一個saga 登錄
...
<Link to="/login2">Saga登錄 </Link>
<Route path="/login2" component={ SagaLogin }/>
...
//SageLogin
import React,{useState} from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom'
// 這裏用的是Hooks 寫起來順手 直接用這個了
function SagaLogin(props) {
let [name,setName] = useState('')
const { location, login, isLogin, loading,error } = props
console.log(props)
const redirect = location.redirect ? location.redirect : '/'
if (isLogin) {
return <Redirect to={ redirect }/>;
}
return (
<div>
<h1>LoginPage</h1>
<p>{name}</p>
{error && <p>{error}</p>}
<input type="text" onChange={(e)=>{setName(e.target.value)}}/>
<button onClick={ () => login(name) }>
{ loading ? '登陸中...' : '登陸' }
</button>
</div>
)
}
// 這裏面的就再也不解釋了 再說react-thunk 解釋過
export default connect(state => (
{
isLogin: state.user.isLogin,
loading: state.user.loading,
error: state.user.error,
}), {
// 這裏調用Saga的login 函數,由於有事件監聽。觸發了loginHandle
login: name => ({ type: 'login', name }),
},
)(SagaLogin)
複製代碼
//Saga 這裏是把邏輯分離了出來,只是爲了更好維護
import { call, put, takeEvery } from 'redux-saga/effects';
// 模擬登陸接口
const UserService = {
login(name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (name === '阿琛') {
resolve({ name: '阿琛' });
} else {
reject('用戶名或密碼錯誤');
}
}, 1000);
});
},
};
// worker saga
function* loginHandle(action) {
console.log('loginHandle');
try {
// 派發清求 到ReduxSage 使得loading爲true
yield put({ type: 'requestLogin' });
// 執行回調函數。獲取結果
const res = yield call(UserService.login, action.name);
// 把結果派發回去
yield put({ type: 'requestSuccess', res });
} catch (err) {
yield put({ type: 'requestFailure', err });
}
}
// watcher saga 事件監聽 監聽login事件
function* mySaga() {
yield takeEvery('login', loginHandle);
}
export default mySaga;
// ReduxSage
import { createStore, combineReducers, applyMiddleware } from 'redux';
import mySaga from './Saga';
import createSagaMiddleware from "redux-saga";
// 建立一箇中間件
const sagaMiddleware = createSagaMiddleware();
// 初始化狀態
const initialLogin = { isLogin: false, loading: false, name: '', error: '' };
// 定義Reducer
// 注意 必須又default 從action 裏面拿到傳遞過來的參數
function loginReducer(state = { ...initialLogin }, action) {
switch (action.type) {
case 'requestLogin':
return { ...initialLogin, loading: true };
case 'requestSuccess':
return { ...state, isLogin: true, loading: false,name: action.name };
case 'requestFailure':
console.log(action)
return {...state,error: action.err}
default:
return state;
}
}
const store = createStore(combineReducers(
{ user: loginReducer }
),
// applyMiddleware(thunk)
applyMiddleware(sagaMiddleware)
);
// 記得運行一下
sagaMiddleware.run(mySaga)
// 若是按看上面的例子了話,記得換一下store 再index.js 裏用provider 來傳遞store
export default store;
複製代碼
Hook 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性
this
import React,{useState} from 'react';
function Counter() {
// useState會返回兩個,一個是當前值。一個是改變它的函數
let [state,setState] = useState({num: 0})
let [num,setNum] = useState(0)
// 兩種方式 看我的喜愛
return (
<div> <p>{state.num}</p> <p>{num}</p> <button onClick={()=>{setState({num: state.num +1})}}>+</button> <button onClick={()=>{setNum(num + 1)}}>+</button> </div>
)
}
複製代碼
// alter 捕得到是點擊時候得狀態
function Counter2 () {
// useState 返回兩個,一個是當前狀態的屬性,一個是修改狀態的方法。
let [state, setState] = useState({num: 0})
let changeLater = ()=>{ // 這個alter 會捕獲當前得狀態。即點擊按鈕時候得狀態
setTimeout(()=>{
alert(state.num)
}, 2000)
}
return (
<div> <p>{ state.num }</p> <button onClick={ () => setState({num: state.num+1}) }>+</button> <button onClick={ changeLater }>changeLater</button> </div>
)
}
複製代碼
// 改變狀態注意
function Counter3 () {
// useState 返回兩個,一個是當前狀態的屬性,一個是修改狀態的方法。
let [state, setState] = useState({num: 0})
let lazyAdd = ()=>{ // 這個alter 會捕獲 當前得狀態。即點擊按鈕時候得狀態
setTimeout(()=>{
setState({num: state.num+1})
}, 2000)
}
let lazyAdd2 = ()=>{ // 這個alter 會捕獲以後的狀態。
setTimeout(()=>{
setState((state) => ({num: state.num+1}))
}, 1000)
}
return (
<div> <p>{ state.num }</p> <button onClick={ () => setState({num: state.num+1}) }>+</button> <button onClick={ lazyAdd }>lazyAdd</button> <p>若是這樣寫得話,會獲取到點擊時候得狀態在進行改變</p> <button onClick={ lazyAdd2 }>lazyAdd2</button> <p>這樣寫,改變了整個state,就能夠正常相加了</p> </div>
)
}
複製代碼
若是新的 state 須要經過使用先前的 state 計算得出,那麼能夠將函數傳遞給 setState。該函數將接收先前的 state,並返回一個更新後的值
// 惰性state
// initialState 初始狀態參數只會在組件初始渲染得時候調用,後續被忽略
// 且不會本身整合state,須要本身整合
function Counter4 () {
let [state, setState] = useState(function () {
console.log(' 初始化')
return {num: 0, name:'計算器'}
})
return (
<div> <p>{state.num}</p> <button onClick={ () => setState({num: state.num+1}) }>+</button> <p>只會輸出一次log</p> <p>React.StrictMode 模式下會輸出兩次log</p> <p>這是展現他不會整合state</p> <p>{ state.name }:{state.num}</p> <p>若是點擊上面得button,則name會消失,下面這個則不會</p> <button onClick={ () => setState({...state, num: state.num+1}) }>+</button> </div>
)
}
複製代碼
按第一個button 計算器消失的緣由: setState
不會本身整合State
,須要咱們本身手動進行整合
MDN,使用Object.is
比較。
function Counter5 () {
let [state, setState] = useState(function () {
return {num: 0, name:'計算器'}
})
console.log('當state發生改變得時候,會從新得渲染這個函數,則會繼續得輸出')
console.log('Render5 ')
return (
<div> <p>{state.num}</p> <button onClick={ () => setState({...state, num: state.num+1}) }>輸出+</button> <p>點擊這個不會輸出 Render5</p> <button onClick={ () => setState(state) }>不輸出+</button> <p>緣由,內部當發現這個state沒有發生改變得時候,將再也不會渲染這個</p> </div>
)
}
複製代碼
緩存函數
let lastClick;
let lastClick2;
function Counter7(callback, deps) {
let [state, setState] = useState({num: 0})
const contralAdd = ()=> setState({num: state.num+1})
console.log(lastClick === contralAdd)
lastClick = contralAdd // 一直輸出false 證實不是同一個函數
/**********************************************************************************/
const contralAdd2 = useCallback(
()=> setState({num: state.num+1}), []
// 這個數組稱之爲依賴數組,裏面放屬性,屬性發生變化,他就從新渲染
// 若是不寫 state.num 那點擊下面得按鈕只會變化一次。
// 若是寫了得話,則會每次改變都會從新渲染
)
console.log(lastClick2 === contralAdd2)
lastClick2 = contralAdd2 // 一直輸出true 證實是同一個函數
return (
<div> <p>{state.num}</p> <button onClick={ contralAdd }>+</button> <p>這樣得話每次會生成一個新得函數,性能會變低</p> <button onClick={ contralAdd2 }>+</button> <p>這樣得話更具log顯示,得出是一個函數,提升性能</p> </div>
)
}
複製代碼
lastClick === contralAdd false
lastClick2 === contralAdd2 true
複製代碼
緣由:state.num 發生了改變,從新渲染了 Counter。因此第一個輸出false。 第二個由於加了useCallback,依賴數組裏面沒有添加屬性,致使,因此程序認爲他沒有發生改變,因此輸出 true
若是點擊下面的 + 號 log輸出
lastClick === contralAdd false
lastClick2 === contralAdd2 true
複製代碼
它一樣會輸出這個,緣由,若是不寫依賴屬性,那點擊下面得按鈕只會變化一次。而數值變化了。因此第一個爲true 第二個爲false,而且再點擊這個按鈕不會再發生變化
(說的有點不許確,準確來講是發生變化了,由於閉包的緣由,致使第二個函數的state.num
一直指向初始化的0 因此纔沒有改變)
添加依賴屬性以後。輸出結果
lastClick === contralAdd false
lastClick2 === contralAdd2 false
複製代碼
這個不用多說了,依賴屬性發生了改變。致使兩個函數都從新渲染
純函數,作優化,或者叫緩存,緩存組件
import React,{memo,useMemo,useState} from 'react';
function Child() {
console.log('我是Child')
return (
<h2>我是child</h2>
)
}
function Child2(props) {
console.log('render child2')
console.log(props)
return (
<p>我是Child2{props.name}</p>
)
}
Child = memo(Child)
Child2 = memo(Child2)
function MemoApp() {
let [state,setState] = useState({num: 0})
let [name, setName] = useState('測試')
return(
<div>
<p>{state.num}</p>
<button onClick={()=>{setState({num: state.num +1 })}}>+</button>
<br/>
<input type="text" value={name} onChange={(e)=>{setName(e.target.value)}}/>
<Child/>
<Child2 name={name}/>
</div>
)
}
export { MemoApp }
複製代碼
若是不加上memo
,則只要點擊 + 號,Child
就會發生渲染。
通俗解釋:只要上次渲染結果和此次相同。就不會渲染,不然渲染
也是作緩存用的。緩存數據
let lastData
function UseMemoApp() {
let [state, setState] = useState(0)
let [name, setName] = useState('測試')
const data = state
const data2 = useMemo(() => (state), []);
console.log('data===oldData ', data === lastData);
const addClick = useCallback(() => setState(state + 1), [state])
lastData = data;
return(
<div> <p>{state}</p> <p>{data2.state}</p> <button onClick={addClick}>+</button> <p>改變input的值,輸出true 證實數據作了緩存</p> <input type="text" value={name} onChange={(e)=>{setName(e.target.value)}}/> </div> ) } 複製代碼
上面那個userCallback
能夠無視,我就是作了一個緩存。重點關注兩個data
改變了input
內的值以後,log 輸出true,證實數據緩存成功,數據發生改變,輸出false
import React, { useReducer } from 'react';
// 初始化狀態
const initialState = 0;
// 初始一個reducer state 狀態 action 派發狀態
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { number: state.num + 1 };
case 'decrement':
return { number: state.num - 1 };
default:
throw new Error();
}
}
// 把數據變成 對象
function init(initialState) {
return { num: initialState }
}
function ReducerCounter() {
const [state, dispatch] = useReducer(reducer, initialState, init);
return (
<> Count: { state.num } <p></p> <button onClick={ () => dispatch({ type: 'increment' }) }>+</button> <button onClick={ () => dispatch({ type: 'decrement' }) }>-</button> </> ) } export { ReducerCounter } 複製代碼
若是函數的名字以 use 開頭,而且調用了其餘的 Hook,則就稱其爲一個自定義 Hook。
這裏只作一個簡單解釋。後面會詳細介紹
function useStateSelf(initState) {
function init(initialState){
return {number:initialState};
}
// userCallback 作緩存
const reducer = useCallback((state,action) => {
console.log(action)// 看log 輸出判斷 返回值
console.log(state)
return action.payload // 加{} 須要 寫 action.payload
// return payload// 不加{}直接寫action
})
let [state,dispatch] = useReducer(reducer, initState,init)
console.log(dispatch)
function setState(payload) {
console.log(payload) // 整個對象 {number : x}
dispatch({payload})
// dispatch(payload)
}
return [state,setState]
}
// 調用
const [state, setState] = useStateSelf(initialState);
return(
<div> <p>自定義hooks</p> <p>{state.number}</p> <button onClick={() => setState({number: state.number + 1 })}>+</button> <button onClick={() => setState({number: state.number - 1 })}>-</button> </div>
)
複製代碼
思路分析,首先參照useState
的使用,須要傳入一個狀態(本例子只考慮數值,沒考慮對象)。返回一個數組,狀態和改變狀態的函數.