架手架 create-react-app demo 后生成public文件
-----> public文件 是放一些静态资源的,是不会参与构建的,你输入是怎样,最终打包构建仍是怎样,不像src文件下的代码会参与打包构建test ls
---> 出现 apitest ls api/
---> api文件下出现data.jsontest vi api/data.json
-----> 进入到 .json 文件左下角 :q
------> 就是退出该文件"proxy":{
"/api":{ (例子)
"target": "http://localhost:5000"
}
}
复制代码
public文件夹
mock文件夹
data.json文件
{"data":"test"}
localhost:3000/mock/data.json 这样也能拿到mock的模拟数据
public文件下的内容会原封不动的打包到整个项目当中,是一些静态资源
复制代码
todos是父级传给子级的props todos = [{id:1,text:"1111"},{id:2,text:"2222"}]
todos.map(todo=>{
return <Todo key={todo.id} {...todo}/>
})
复制代码
const todo = {
id: this.indexId++,
text: text,
completed: true
}
const newTodoArr = [...this.state.todoArr,todo]
复制代码
若是用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉,这用来修改现有对象部分的部分属性就很方便了。
const newTodo = this.state.todos.map(item=>{
return item.id === 1? {...item, completed: !item.completed} :item
})
复制代码
index.js文件下:
import {ADD_TODO, ………等} from "./actionTypes";
export addTodo = (text)=>({
type:ADD_TODO,
text
})
……………等……………
通常状况下 type 同方法名称相同而且改为大写便可
actionTypes.js文件下:
export const ADD_TODO = "ADD_TODO";
…… …… ……
复制代码
const initState = {
filter: "",
todos: [],
text: ""
}
const todoApp = (state = initState, action)=>{
switch(action.type){
case ADD_TODO:
return {
...state,
todos:[
...state.todos,
{
id: action.id,
text:action.text,
completed: false
}
]
}
case SET_FILTER:
…… ……
case SET_TODO_TEXT:
…… ……
default :
return state
}
}
export default todoApp;
reducer里的state是不能直接操做的,必须返回一个新的state,返回新的state对象的方法有不少,好比 Object.assign({},……)也是其中一种方法
复制代码
其中一个例子,分解reducer中的 SET_TODO_TEXT
新建一个text.js 用来拆分 SET_TODO_TEXT
import {…… ……} from ""../actions/actionTypes;
const text = (state = "", action)=>{
switch (action.type){
case SET_TODO_TEXT:
return action.text
default:
return state
}
}
export default text
分解完上面那个大reducer以后,把reducer文件夹中的index里面的大reducer删掉,逐步引入拆分的各个小的reducer
index.js:
import { combineReducers } from "redux";
import text from "./text";
import …… ……
export default combineReducers({
text,
…… ……
});
必须安装redux这个依赖 npm install redux 由于create-react-app 并无安装redux
复制代码
import { createStore } from "redux";
import rootReducer from "./reducers";
import { addTodo ,…… …… } from "./actions";
export const store = createStore(rootReducer);
// 获得初始的state
store.getState()
// 订阅state的变化
store.subscribe(()=>{
console.log(store.getState)
})
// 发生actions
store.dispatch(addTodo("11111"));
复制代码
向根组件注入 Store -> Provider 组件
链接React组件和redux状态层 -> connect
获取React组件所需的State和Actions -> map api
先安装react-redux这个依赖 npm install react-redux 由于create-react-app 并无安装react-redux
例子:在src下建立containers文件夹,建立TodoListContainer.js文件 容器组件
import { connect } from "react -redux";
import { toggleTodo } from "../actions";
import TodoList from "../components/TodoList";
const mapStateToProps = (state)=>({
todos: state.todos
})
const mapDispatchToProps = (dispatch)=>({
toggleTodo: id => dispatch(toggleTodo(id))
})
export default connect(
mapStateToProps, //redux中的state映射到react组件当中的props上
mapDispatchToProps //react组件须要使用的action方法,映射到组件的props当中
)(TodoList);
connect()属于高阶组件,将完成react层和redux层的逻辑链接
例子:在src下的components文件夹的App.js文件下 引入containers里的容器组件
import { TodoListContainer } from "../containers/TodoListContainer";
import …… ……
return (
<div>
<TodoListContainer/>
…… ……
</div>
)
例子:在src下的index.js 入口文件
import { createStore } from "redux";
import { Provider } from "react-redux";
import rootReducer from "./reducers";
const store = createStore(rootReducer);
<Provider store={store}>
<App/>
</Provider>
复制代码
例子:
const getTodosSuccess = (data)=>({
type: "TODOS_SUCCESS",
data
})
const getTodosFailure = (error)=>({
type: "TODOS_FAILURE",
error
})
export const getTodos = ()=> dispatch =>{
return new Promise((resolve,reject) => {
axios.get({ url: 'xxxxxxxxx'}).then(res=>{
dispatch(getTodosSuccess(res))
resolve(res)
}).catch(err=>{
dispatch(getTodosFailure(err))
reject(err)
})
})
}
getTodos()返回值是一个函数,这个函数咱们是没法处理的,因此要借助redux的中间件来完成异步action这个操做
最经常使用的是 npm install redux-thunk
复制代码
例子: 在src目录的 index.js下
import thunkMiddleware from "redux-thunk";
import { createStore, applyMiddleware } from "redux";
const store = createStore(rootReducer, applyMiddleware(thunkMiddleware))
这样建立出来的store 就能处理异步action了
复制代码
在github上寻找 redux-devtools-extension
找到 Advanced store setup
if you setup your store with middleware and enhancers,change:
xxxxxxxxxxxxxxxxxxxxxxxxxxx 这里的东西能够用,复制下来
在src目录的 index.js下
import { compose } from "redux"; //compose这个函数能够帮助咱们将多个enhancers组合到一块儿,compose是个加强器能够增长store的功能
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION__COMPOSE__ || compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(thunkMiddleware))
);
复制代码
按照类型:
action/
-- action1.js action2.js
components/
-- component1.js component2.js
reducers/
-- reducer1.js reducer2.js index.js(全部reducer组合成一个)
containers/ (容器型组件)
-- containser1.js containser2.js
按照功能模块:(存在不一样模块之间状态互相耦合的场景)
feature1/
-- components/
-- Container.js
-- actions.js
-- reducer.js
feature2/
-- components/
-- Container.js
-- actions.js
-- reducer.js
Ducks: …………
复制代码
例子:redux-thunk 能够处理函数类型的action了
View -> ( mid1 -> mid2 -> …… …… ) -> reducer //页面触发action 会逐步通过这些中间件,加强dispatch,dispatch所作的事就是派发action
Function:({getState, dispatch}) => next => action
在src文件夹下 新建 Middleware文件夹->logger.js
// 打印action, state 的logger中间件
const logger = ({getState, dispatch}) => next => action => {
console.group(action.type);
console.log("dispatching:", action);
const result = next(action);
console.log("next state:", getState());
consoel.groupEnd();
return result;
}
export default logger;
在index.js文件下 ,把本身写的中间件加进去
import loggerMiddleware from "./middleware/logger";
const store = createStore(
…… ……
applyMiddleware(…… ……,loggerMiddleware)
)
复制代码
store enhancer 通常结构:
function enhancerCreator(){
return createStore => (...arg) => {
// do something based old store
// return a new enhanced store
}
}
在src文件夹下 新建enhancer文件夹 -> logger.js
// 打印action, state 的加强器
const logger = createStore => (...arg) => {
const store = createStore(...arg);
const dispatch = (action) => {
console.group(action.type);
console.log("dispatching:", action);
const result = store.dispatch(action);
console.log("next state:", store.getState());
consoel.groupEnd();
return result;
}
return {...store, dispatch} // 把新的dispatch方法覆盖到原来对象的dispatch
}
export default logger;
在index.js文件下 ,把本身写的enhancer加进去
import loggerEnhancer from "./enhancer/logger";
const store = createStore(
…… ……
compose(applyMiddleware(thunkMiddleware), loggerEnhancer)
)
compose是能够把多个 store enhancer 进行组合的
在平时中应该尽可能多使用 Middleware 来加强dispatch的能力, 慎用 store enhancer 记住!
复制代码
npm install immutable
npm install redux-immutable
import Immutable from "immutable";
import { combineReducers } from "redux-immutable";
const initState = {
…… ……,
isFetching: false,
todos: {
……,
data: []
}
}
//这样就建立了一个不可变的 immutable 对象
const state = Immutable.fromJS(initState)
//设置属性,一次只能设置一个key
state.set("isFetching", true);
//merge能够一下设置多个
state.merge({
isFetching: false,
todos: Immutable.fromJS(action.data) // 数据类型必定要统一用immutable的类型
});
//get获得属性的值
state.get("isFetching");
//getIn 从外层到内层逐层获取
state.getIn(["todos","data"]);
//把immutable对象转化成普通JS对象
state.toJS();
由于在 mapStateToProps,…… 的对象要普通JS对象才能使用
在reducers文件下的 index.js中
import { combineReducers } from "redux-immutable"; // redux中的combineReducers只能识别普通JS对象
复制代码
npm install reselect
import { createSelector } from "reselect"
const getFilter = (state) => state.filter;
const getTodos = (state) => state.todos.data;
export const getVisibleTodos = createSelector(
[getTodos,getFilter], //数组里面是其余所依赖的select函数
(todos, filter) => { //前2个函数返回的数据结果,可用来计算
console.log("-----"); //用来测试看有没多余的重复执行
switch(filter){
case: "xxx":
return
case ……:
……
default:
……
}
}
);
只有当select函数确实很是复杂而且整个redux性能有很大的问题才去考虑用reselect,并非必须的
复制代码
react-router
react-router-dom
react-router-native
建议在项目之中使用<BrowserRouter>组件
react-router这个库实现了路由的核心功能,react-router-dom是在react-router之上的一层封装,将react-router的路由功能和web的API进行了绑定
<Router>经常使用组件: <BrowserRouter>
1 . Html5 history API (pushState, repalceState等)
2. 须要Web服务器额外配置
localhost:3000/home localhost:3000/about
<Router>经常使用组件: <HashRouter>
1. 使用url的hash部分做为路由的信息
2. 主要为了兼容老版本浏览器
localhost:3000/#/home localhost:3000/#/about
复制代码
<BrowserRouter>
<Route path="/" exact component={Home}/>
<Route path="/about" component={About}/>
<Route path="/user/:aaa" component={User}/>
…… ……
<BrowserRouter/>
组件中获得传过来的参数: this.props.match.params.aaa
复制代码
<Route exact /> exact : url和path彻底相同的状况下才会匹配成功
import { Switch } from "react-router-dom";
<Switch>
<Route path="/about" component={About}/>
<Route path="/user/:aaa" component={User}/>
<Route path="/" component={Home}/> // 当前面都匹配不成功时,根路径才会被匹配
<Switch/>
Swicth 的功能是会去匹配第一个,被匹配到的<Route />,当第一个<Route/>匹配到的时候,其余的<Rpute>都不会被匹配
复制代码
<Route component />
<Rute render />
<Route children />
<Route path="/about" render={
(props) => <About {...props}/> //props是一个对象,包含了match,location,history
} />
在App.js:
<Router>
<Route path="/user" component={User}>
</Router>
在User.js:
<div>
<Route path={`${this.props.match.path}/:user`} component={UserDetail}>
</div>
在UserDetail.js:
<div>
this.props.match.params.user
</div>
复制代码
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
serviceWorker 离线存储,加载优化的事情, 作项目时没使用到的话要把这句话删掉
复制代码
判断当年程序的执行是开发环境仍是生产环境 && 若是浏览器安装redux的调试工具 window对象下会有redux的调试方法添加进去
if (
process.env.NODE_ENV !== "production" &&
window.__REDUX_DEVTOOLS_EXTENSION__
){
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; // 做为store的加强器
store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk))); // 将REDUX_DEVTOOLS的功能加强到store当中
} else {
store = createStore(rootReducer, applyMiddleware(thunk));
}
复制代码
axios 优缺点:
- 从 node.js 建立 http 请求。
- 支持 Promise API。
- 提供了一些并发请求的接口(重要,方便了不少的操做)。
- 在浏览器中建立 XMLHttpRequests。
- 在 node.js 则建立 http 请求。(自动性强)
- 支持 Promise API。
- 支持拦截请求和响应。
- 转换请求和响应数据。
- 取消请求。
- 自动转换 JSON 数据。
- 客户端支持防止CSRF。
- 客户端支持防护 XSRF。
Fetch 连接 https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch (MDN)
axios既提供了并发的封装,也没有fetch的各类问题,并且体积也较小,当之无愧如今最应该选用的请求的方式。
复制代码
文件 ./entities/products.js
export const schema = {
name: "products",
id: "id"
}
…… …… ……
*****************************************************
文件 ./redux/home.js
import FETCH_DATA from "../middleware/api"
import schema from "./entities/products"
export const types = {
FETCH_LIKES_REQUEST:"FETCH_LIKES_REQUEST",
FETCH_LIKES_SUCCESS:"FETCH_LIKES_SUCCESS",
FETCH_LIKES_FAILURE:"FETCH_LIKES_FAILURE"
}
export const actions = {
loadLikes: ()=>{
return (dispatch, getState) => {
const endpoint = url.getProductList(0,10); // utils中的方法返回一个url路径
return dispatch(fetchLikes(endpoint));
}
}
}
const fetchLikes = (endpoint)=>({
[FETCH_DATA]: {
types:[
types.FETCH_LIKES_REQUEST,
types.FETCH_LIKES_SUCCESS,
types.FETCH_LIKES_FAILURE
],
endpoint,
schema
},
});
…… …… ……
*****************************************************
文件 middleware/api.js
export const FETCH_DATA = "FETCH_DATA";
export default store => next => action => {
const callAPI = action[FETCH_DATA];
if(typeof callAPI === "undefined"){
return next(action);
}
const { endpoint, schema, types } = callAPI
if(typeof endpoint !== "string"){
throw new Error("endpoint要为字符串类型的url");
}
if(!schema){
throw new Error("必须指定领域实体的schema");
}
if(!Array.isArray(types) && types.length !== 3){
throw new Error("须要指定一个包含了3个action type的数组");
}
if(!types.every(type => typeof type === "string")){
throw new Error("action types 必须为字符串类型");
}
// 里面有新东西,加强dispatch()
const actionWith = (data) =>{
const finalAction = {...action, ...data};
delete finalAction[FETCH_DATA];
return finalAction
}
const [requestType, successType, failureType] = types;
next(actionWith({type: requestType}));
return fetchData(endpoint, schema).then(
response => next(actionWith({
type: successType,
response
})),
error => next(actionWith({
type: failureType,
error: error.message || "获取数据失败"
}))
)
};
// 执行网络请求
const fetchData = (endpoint, schema) => {
return get(endpoint).then(data => {
return normalizeDate(data,schema)
})
}
// 根据schema,将获取的数据扁平化处理
const normalizeDate = (data,schema) => {
const { id,name } = schema;
let kvObj = {};
let ids = [];
if(Array.isArray(data)){
data.forEach(item => {
kvObj[item[id]] = item;
ids.push(item[id]);
})
}else{
kvObj[data[id]] = data;
ids.push(data[id]);
}
return {
[name]: kvObj,
ids
}
}
复制代码
// 对于一些变化频率不高的数据不须要重复请求,好比首页进详情页,详情页又回到首页的某些数据,能够直接拿redux里的缓存
this.removeListener = false;
componentDidMount(){
// 凡是要加载数据都要多想一想什么状况要加载,何时能够避免运算一些逻辑
if(this.props.pageCount < 3){
document.addEventListener("scroll", this.handleScroll);
}else{
this.removeListener = true;
}
// 不是每次都要加载数据,经过一些判断
if(this.props.pageCount == 0){
this.props.fetchData();
}
}
componentDidUpdate(){
if(this.props.pageCount >= 3 && !this.removeListener){
document.removeEventListener("scroll", this.handleScroll);
this.removeListener = true;
}
}
componentWillUnmount(){
if(!this.removeListener){
document.removeEventListener("scroll", this.handleScroll);
}
}
复制代码
const finalAction = {...action, ...data}
delete finalAction[FETCH_DATA]
return finalAction
var a = {"a":"a","b":"b","c":"c"}
delete a.a
a = {b: "b", c: "c"};
复制代码
Refs 提供了一个得到DOM节点或者建立在render方法中的React元素的方法前端
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
访问Refs:
当一个ref经过render放入一个元素中,一个对节点的引用能够经过ref的current属性获得;
const node = this.myRef.current;
好比:
this.myRef.current.offsetHeight --- 就能获得DOM节点的高度
复制代码
const actionWith = data => {
const finalAction = {...action, ...data}
delete finalAction[FETCH_DATA]
return finalAction
}
var a = {"sdf":111,"d":"sdfsdfsdf","23":11111}
delete a["d"]
a = {23: 11111, sdf: 111}
复制代码
<Router>
<Switch>
<Route />
………… ……
</Switch>
</Router>
handleBack = () => {
this.props.history.goBack();
}
须要在路由包裹里面才能生效
复制代码
this.props.history.push("/");
this.props.history.push("/search");
<Link to="/search" className="">xxxxx</Link>
<Route path="/search" component={Search}/>
复制代码
<img alt="" />
复制代码
const rootReducer = combineReducers({
entities,
login,
…… …… ……… ……
}
const initialState = {
username: localStorage.getItem("username") || "",
password: ''
}
const login = (state=initialState, action) => {
switch(){
case:
…… …… ……
}
}
export default login;
login: () => {
return (dispatch, getState) => {
const {username, password} = getState().login;
dispatch({type:…… ……});
}
}
getState() 获得reducer中的全部数据,login是combineReducers其中一个,这又和上层Login组件有着联系
connect(
mapStateToProps,
mapDispatchToProps
)(Login);
复制代码
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { actions as loginActions, getUsername, …… } from ……
// selectors
export const getUsername = (state) => state.login.username;
// action creators
export const actions = {
login:() => …… ……
}
const mapStateToProps = (state, props) => {
return {
username: getUsername(state), // 这里的state是数据,因此并不用state()获得,直接传递便可
…… …… ……
}
}
const mapDispatchToProps = (dispatch, props) => {
return {
loginActions: bindActionCreators(loginActions, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);
复制代码
handleClick = (item) => {
this.props.onClickItem(item);
}
<span key={item.id}
className="popularSearch__item"
onClick={this.handleClick.bind(this, item)}
>{item.keyword}</span>
onClick事件要往其父级传参
复制代码
const { location: {state} } = this.props;
if(state && state.from){
return <Redirect to={state.from}/>
}
复制代码
<Router>
<Switch>
<Route exact path="/" component={Home}/>
…… …… ……
<PrivateRoute path="/user" component={User}/>
<PrivateRoute path="/purchase/:id" component={Purchase}/>
</Switch>
</Router>
class PrivateRoute extends React.Component{
render(){
const { component: Component, login, ...rest } = this.props;
return (
<Route
{...rest}
render={(props) => {
return login ? (<Component {...props}/>) : (<Redirect to={{
pathname: "/login",
state: {from: props.location}
}}/>)
}}
/>
)
}
}
…… …… ……
…… …… ……
复制代码
const {
data: { title, statusText, orderPicUrl, channel, text, type, commentId }, isCommenting
} = this.props;
复制代码
typeof new Date() "object"
typeof +new Date() "number"
new Date()
Mon Oct 14 2019 09:32:06 GMT+0800 (中国标准时间)
+new Date()
1571016720402
复制代码
const mapStateToProps = (state, props) => {
const productId = props.match.params.id;
return {
product: getProduct(state, productId),
…… ……
}
};
const mapDispatchToProps = (dispatch) => {
return {
purchaseActions: bindActionCreators(purchaseActions, dispatch),
detailActions: bindActionCreators(detailActions, dispatch)
}
}
复制代码
项目跑起来后,network里会加载个 bundle.js 这个js文件涵盖了项目中全部js资源和全部js代码,所以这个bundle.js会比较大在项目里不点点击切换页面路由,可是network中并无加载新的js代码,依然只有bundle.js,显然对性能是有影响的, 咱们要作的就是,访问不一样页面加载不一样页面的JS代码node
import("../Login") 传入组件的路径,这里的import并非一个函数而是一个运算符,返回结果是一个promise对象,
当组件加载完成以后,promise状态会变成完成,那么咱们就能够正常使用
咱们须要定义一个高阶组件 function(){ import("../Login") }, 调用的时候会执行里面的代码
复制代码
const User = AsyncComponent(() => import("../User"));
const Purchase = AsyncComponent(() => import("../Purchase"));
<Router>
<Switch>
<PrivateRoute path="/user" component={User} />
<PrivateRoute path="/purchase/:id" component={Purchase}/>
…… …… ……
</Switch>
</Router>
export default function asyncComponent(importComponent){
class AsyncComponent extends React.Component{
constructor(props){
super(props);
this.state = {
component: null
}
}
componentDidMount(){
importComponent().then((mod) => {
this.setState({
component: mod.default
})
})
}
render(){
const C = this.state.component;
return C ? <C {...this.props}/> : null
}
}
return AsyncComponent;
}
改为这样以后,在network里面能够看到 bundle.js 这是项目的骨架代码,1.chunk.js这是首页的js代码,
等下每点去另外一个页面都会加载一个不一样页面的 chunk.js
npm run build 以后
也能够在控制台看到代码被编译以后是分片的…… 0,1,2,3……chunk.js,骨架代码是 main.js
复制代码
<div>
<Count /> // 好比设置 setInterval定时器1000ms 执行一次Count
<Posts />
</div>
这样本没有什么问题,可是,一旦 Posts 的 mapStateToProps 是须要产生衍生数据的,即便 Posts 的 reducer 没有变化,
也仍是要随着 Count 每 1000ms 计算,浪费性能
Count 每 1000ms 执行一次自加,不光 Count 的 reducer 会过一遍 mapStateToProps,
Posts 的 reducer 也会过一遍。这是必然的,由于 connect 就是这样设计的。
const mapStateToProps = state => {
const postsById = state.postsById
const usersById = state.usersById
const postListing = state.postListing
return {
posts: postListing.map(id => {
const post = postsById[id];
return {...post, user: usersById[post.author]}
})
}
}
使用 reselect 优化:
这样子,就能够作到:只有 postsById、usersById、postListing 其中之一变化时才会从新计算 posts
const getListing = createSelector(
state => state.postsById,
state => state.usersById,
state => state.postListing,
(posts, users, listing) => listing.map(id => {
const post = posts[id];
return {...post, user: users[post.author]}
})
);
const mapStateToProps = (state) => {
return {posts: getListing(state)};
};
复制代码
前端react QQ群:
788023830
----React/Redux - 地下老英雄
前端交流 QQ群:
249620372
----FRONT-END-JS前端
(咱们的宗旨是,为了加班,为了秃顶……,仰望大佬),但愿小伙伴们加群一块儿学习react