前置:对 react、hook 丁丁了解
用过 as well 、看过 as good 、用过Class as not bad
从易到难,从简到繁,一步步入坑。开挂可能会使你懵逼。
Lv 1 ~ Lv9 (修仙) ~ Lv 17 (修神) ~ Lv 19 (永恒)
Lv1 用useState简单实现
import React, { useState } from 'react';
function App() {
const [data, setData] = useState({ hits: [] });
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
Lv2 useEffect 和 axios请求
使用 useEffect() 应该抛弃生命周期思维!没有生命周期、没有生命周期、没有生命周期。
记住:useEffect() 监听了所有 state 状态,任何更新都会被触发。
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios('//api.json/search?query=redux');
setData(result.data);
});
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
Lv3 补充 useEffect(fn, 参数二)
参数二:触发条件 [state, ...] ,只有 [] 指定的内部状态更新才触发。
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios('/api.json/search?query=redux');
setData(result.data);
}, []);
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
Lv4 铺垫优化-抽离请求
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(() => {
const fetchData = async () => {
const result = await axios('/api.json/api/v1/search?query=redux');
setData(result.data);
};
fetchData();
}, []);
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
Lv5 添加请求参数 query
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
useEffect(() => {
const fetchData = async () => {
const result = await axios('/api.json/search?query=redux');
setData(result.data);
};
fetchData();
}, []);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</Fragment>
);
}
export default App;
Lv6 参数 query 带入请求
Input onChange 事件任意调动 state ,又因 state 更新能触发 useEffect()
...
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
useEffect(() => {
const fetchData = async () => {
const result = await axios(`/api.json/search?query=${query}`);
setData(result.data);
};
fetchData();
}, []);
return (
...
);
}
export default App;
Lv7 useEffect(fn, []) 监听 query
监听 query 状态,更新触发 useEffect() 。
当前没有特殊效果同 Lv6 无差,继续升级...
...
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
useEffect(() => {
const fetchData = async () => {
const result = await axios(`/api.json/search?query=${query}`);
setData(result.data);
};
fetchData();
}, [query]);
return (
...
);
}
export default App;
Lv8 添加事件搜索参数 search
点击 Button 把 query 赋值给 search 但是 useEffect() 还是会被触发,为什么呢?
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [search, setSearch] = useState('');
useEffect(() => {
const fetchData = async () => {
const result = await axios(`/api.json/search?query=${query}`);
setData(result.data);
};
fetchData();
}, [query]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="button" onClick={() => setSearch(query)}>
Search
</button>
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</Fragment>
);
}
Lv9 替换参数二, useEffect(fn, [参数二])
因为 参数二 变更为监听 search ,所以 Input 改变 query 后不会触发 useEffect()
...
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [search, setSearch] = useState('redux');
useEffect(() => {
const fetchData = async () => {
const result = await axios(`/api.json/search?query=${search}`);
setData(result.data);
};
fetchData();
}, [search]);
return (
...
);
}
export default App;
你已成功渡劫成仙,可以进阶修神,也可以称霸仙界。
称霸仙界,以下内容可以跳过~
Lv10 铺垫优化-独立参数 url
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [url, setUrl] = useState('/api.json/search?query=redux');
useEffect(() => {
const fetchData = async () => {
const result = await axios(url);
setData(result.data);
};
fetchData();
}, [url]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button
type="button"
onClick={() =>setUrl(`/api.json/search?query=${query}`)}
>
Search
</button>
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</Fragment>
);
}
Lv11 添加参数 isLoading
添加页面 Loading 状态
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [url, setUrl] = useState('/api.json/search?query=redux',);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const result = await axios(url);
setData(result.data);
setIsLoading(false);
};
fetchData();
}, [url]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button
type="button"
onClick={() =>setUrl(`/api.json/search?query=${query}`)}
>
Search
</button>
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default App;
Lv12 添加参数 isError
添加页面 Error 状态
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [url, setUrl] = useState('/api.json/search?query=redux');
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(url);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button
type="button"
onClick={() =>
setUrl(`/api.json/search?query=${query}`)
}
>
Search
</button>
{isError && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default App;
Lv13 封装 form 表单
function App() {
...
return (
<Fragment>
<form onSubmit={() => {
setUrl(`/api.json/search?query=${query}`);
}}>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isError && <div>Something went wrong ...</div>}
...
</Fragment>
);
}
Lv14 优化-阻止事件冒泡
function App() {
...
return (
<Fragment>
<form onSubmit={event => {
setUrl(`/api.json/search?query=${query}`);
event.preventDefault();
}}>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isError && <div>Something went wrong ...</div>}
...
</Fragment>
);
}
Lv15 优化-抽离请求部分封装 searchApi 方法
封装请求相关代码封装,返回数据:data、isLoading、isError、setUrl
const searchApi = () => {
const [data, setData] = useState({ hits: [] });
const [url, setUrl] = useState('/api.json/search?query=redux');
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(url);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
return [{ data, isLoading, isError }, setUrl];
}
调用 searchApi 方法。
doFetch 可以更新 url 。 因为 url 与 query 关联,所以 Input 可以更新到 url 并反馈给 searchApi 内部。
function App() {
const [query, setQuery] = useState('redux');
const [{ data, isLoading, isError }, doFetch] = searchApi();
return (
<Fragment>
<form onSubmit={event => {
doFetch(`/api.json/search?query=${query}`);
event.preventDefault();
}}>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
...
</Fragment>
);
}
Lv16 优化-更进一步 可控参数 initialUrl、initialData
由 searchApi 方法调用者向 searchApi 内释放参数。
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
const searchApi = (initialUrl, initialData) => {
const [data, setData] = useState(initialData);
const [url, setUrl] = useState(initialUrl);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(url);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
return [{ data, isLoading, isError }, setUrl];
};
function App() {
const [query, setQuery] = useState('redux');
const [{ data, isLoading, isError }, doFetch] = searchApi(
'/api.json/search?query=redux',
{ hits: [] },
);
return (
<Fragment>
<form
onSubmit={event => {
doFetch(`/api.json/search?query=${query}`);
event.preventDefault();
}}
>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isError && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default App;
你已修成神识,再往上就是永恒了。
useReducer 相似于 useState,但更建议使用 useReducer
Lv17 新角色-useReducer
useReducer((参数一,参数二) => { return 结果 }, 初始参数) 执行结果同 useState,返回:当前state 和 一个 set 方法
import React, {
Fragment,
useState,
useEffect,
useReducer,
} from 'react';
import axios from 'axios';
const dataReducers = (state, action) => {
...
};
const searchApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataReducers, {
isLoading: false,
isError: false,
data: initialData,
});
...
};
Lv18 优化-使用 dispatch
useReducer() 执行结果:当前state 和 一个 set 方法赋给 dispatch
const dataReducers = (state, action) => {
...
};
const searchApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataReducers, {
isLoading: false,
isError: false,
data: initialData,
});
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'FETCH_INIT' });
try {
const result = await axios(url);
dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
} catch (error) {
dispatch({ type: 'FETCH_FAILURE' });
}
};
fetchData();
}, [url]);
...
};
Lv19 优化-返回 相对应状态 和 setUrl
const searchApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataReducers, {
isLoading: false,
isError: false,
data: initialData,
});
...
return [state, setUrl];
};
补充 dataReducers 条件语句
const dataReducers = (state, action) => {
switch (action.type) {
case 'FETCH_INIT':
return { ...state };
case 'FETCH_SUCCESS':
return { ...state };
case 'FETCH_FAILURE':
return { ...state };
default:
throw new Error();
}
};
完善 dataReducers 条件语句
const dataReducers = (state, action) => {
switch (action.type) {
case 'FETCH_INIT':
return {
...state,
isLoading: true,
isError: false
};
case 'FETCH_SUCCESS':
return {
...state,
isLoading: false,
isError: false,
data: action.payload,
};
case 'FETCH_FAILURE':
return {
...state,
isLoading: false,
isError: true,
};
default:
throw new Error();
}
};
最后的修善
const searchApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataReducers, {
isLoading: false,
isError: false,
data: initialData,
});
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
dispatch({ type: 'FETCH_INIT' });
try {
const result = await axios(url);
if (!didCancel) {
dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
}
} catch (error) {
if (!didCancel) {
dispatch({ type: 'FETCH_FAILURE' });
}
}
};
fetchData();
return () => {
didCancel = true;
};
}, [url]);
return [state, setUrl];
};
文章译自 robinwieruch.de ,和自己的一些理解。
第一次踩坑实战,有误处感放指正。
Lv 1 ~ Lv9 : 基础,还是以实用上手为主。 Lv9 ~ Lv 17 : 思维,个人代码习惯, 复用性和统筹。 Lv 17 ~ Lv 19 : 高阶,对 Redux 和 Reducer 的理解。