Ein Versuch einer erbärmlichen Ähnlichkeit mit dem useSelector, useDispatch-Hooks, wie in React-Redux.
Die meisten von uns sind auf Redux gestoßen, und diejenigen, die es in ReactJS verwendet haben, konnten den useSelector, useDispatch-Hooks spüren, ansonsten über mstp, mdtp + HOC connect. Was ist mit schlank? Sie können es vermasseln oder etwas Ähnliches zum Verbinden finden, wie z. B. svelte-redux-connect, und riesige Konstruktionen beschreiben, die wir an dieselbe Verbindung senden:
const mapStateToProps = state => ({
users: state.users,
filters: state.filters
});
const mapDispatchToProps = dispatch => ({
addUser: (name) => dispatch({
type: 'ADD_USER',
payload: { name }
}),
setFilter: (filter) => dispatch({
type: 'SET_FILTER',
payload: { filter }
})
});
Nur ein paar gruselige Rückblenden bis Mitte 2018, vor der Einführung von Hooks :). Ich möchte schlanke Haken. Was können wir daraus ziehen? Hmm ... sveltes Laden ist global, es werden keine Anbieter mit einem Kontext benötigt (nur ein Scherz, sie werden benötigt, um Kontexte zu trennen, aber lassen Sie uns sie vorerst rauswerfen). Es bedeutet so: Wir erstellen einen Redux-Store, dann werden wir versuchen, unsere erbärmlichen Hooks zu schreiben, um die Verwendung zu vereinfachen.
Also unsere Konstanten:
//constants.js
export const GET_USER = '@@user/get'
export const FETCHING_USER = '@@user/fetch'
export const SET_USER = '@@user/set'
Reduzierer:
//user.js
import {FETCHING_USER, SET_USER} from "./constants";
const initialState = {
user: null,
isFetching: false
}
export default function user(state = initialState, action = {}){
switch (action.type){
case FETCHING_USER:
case SET_USER:
return {
...state,
...action.payload
}
default:
return state
}
}
Aktionen:
//actions.js
import {FETCHING_USER, GET_USER, SET_USER} from "./constants";
export const getUser = () => ({
type: GET_USER
})
export const setUser = (user) => ({
type: SET_USER,
payload: {
user
}
})
export const setIsFetchingUser = (isFetching) => ({
type: FETCHING_USER,
payload: {
isFetching
}
})
Selektoren. Kehren wir separat zu ihnen zurück:
//selectors.js
import {createSelector} from "reselect";
import path from 'ramda/src/path'
export const selectUser = createSelector(
path(['user', 'user']),
user => user
)
export const selectIsFetchingUser = createSelector(
path(['user', 'isFetching']),
isFetching => isFetching
)
Und die wichtigsten Mähdrescherreduzierer:
//rootReducer.js
import {combineReducers} from "redux";
import user from "./user/user";
export const reducers = combineReducers({
user
})
Jetzt müssen wir die Redux-Saga anhängen und als API haben wir https://randomuser.me/api/ . Während des Testens des gesamten Prozesses funktionierte diese API sehr schnell und ich wollte mir den Lader unbedingt länger ansehen (jeder hat seinen eigenen Masochismus), also machte ich das Timeout für 3 Sekunden zu einem Versprechen.
//saga.js
import {takeLatest, put, call, cancelled} from 'redux-saga/effects'
import {GET_USER} from "./constants";
import {setIsFetchingUser, setUser} from "./actions";
import axios from "axios";
const timeout = () => new Promise(resolve => {
setTimeout(()=>{
resolve()
}, 3000)
})
function* getUser(){
const cancelToken = axios.CancelToken.source()
try{
yield put(setIsFetchingUser(true))
const response = yield call(axios.get, 'https://randomuser.me/api/', {cancelToken: cancelToken.token})
yield call(timeout)
yield put(setUser(response.data.results[0]))
yield put(setIsFetchingUser(false))
}catch (error){
console.error(error)
}finally {
if(yield cancelled()){
cancelToken.cancel('cancel fetching user')
}
yield put(setIsFetchingUser(false))
}
}
export default function* userSaga(){
yield takeLatest(GET_USER, getUser)
}
//rootSaga.js
import {all} from 'redux-saga/effects'
import userSaga from "./user/saga";
export default function* rootSaga(){
yield all([userSaga()])
}
Zum Schluss den Store initialisieren:
//store.js
import {applyMiddleware, createStore} from "redux";
import {reducers} from "./rootReducer";
import {composeWithDevTools} from 'redux-devtools-extension';
import {writable} from "svelte/store";
import createSagaMiddleware from 'redux-saga';
import rootSaga from "./rootSaga";
const sagaMiddleware = createSagaMiddleware()
const middleware = applyMiddleware(sagaMiddleware)
const store = createStore(reducers, composeWithDevTools(middleware))
sagaMiddleware.run(rootSaga)
// store
const initialState = store.getState()
// writable store useSelector
export const useSelector = writable((selector)=>selector(initialState))
// writable store useDispatch,
//
export const useDispatch = writable(() => store.dispatch)
// store
store.subscribe(()=>{
const state = store.getState()
// store useSelector, ,
// ,
useSelector.set(selector => selector(state))
})
. 18 . , , , - useSelector 3 store - ? , , . , store , , . , , :)
, ?
c useDispatch. svelte-store
export const useDispatch = () => store.dispatch
, useSelector store bindings, useDispatch - , . useDispatch App.svelte:
<!--App.svelte-->
<script>
import {getUser} from "./store/user/actions";
import {useDispatch} from "./store/store";
import Loader from "./Loader.svelte";
import User from "./User.svelte";
//
const dispatch = $useDispatch()
const handleClick = () => {
//
dispatch(getUser())
}
</script>
<style>
.wrapper {
display: inline-block;
padding: 20px;
}
.button {
padding: 10px;
margin: 20px 0;
border: none;
background: #1d7373;
color: #fff;
border-radius: 8px;
outline: none;
cursor: pointer;
}
.heading {
line-height: 20px;
font-size: 20px;
}
</style>
<div class="wrapper">
<h1 class="heading">Random user</h1>
<button class="button" on:click={handleClick}>Fetch user</button>
<Loader/>
<User/>
</div>
. Fetch user, GET_USER. Redux-dev-tools - , . network - , :
. useSelector:
<!--Loader.svelte-->
<script>
import {useSelector} from "./store/store";
import {selectIsFetchingUser} from "./store/user/selector";
// store ,
// , :3
$: isFetchingUser = $useSelector(selectIsFetchingUser)
</script>
<style>
@keyframes loading {
0% {
background: #000;
color: #fff;
}
100% {
background: #fff;
color: #000;
}
}
.loader {
background: #fff;
box-shadow: 0px 0px 7px rgba(0,0,0,0.3);
padding: 10px;
border-radius: 8px;
transition: color 0.3s ease-in-out, background 0.3s ease-in-out;
animation: loading 3s ease-in-out forwards;
}
</style>
{#if isFetchingUser}
<div class="loader">Loading...</div>
{/if}
. store , :
<!--User.svelte-->
<script>
import {useSelector} from "./store/store";
import {selectIsFetchingUser,selectUser} from "./store/user/selector";
$: user = $useSelector(selectUser)
$: isFetchingUser = $useSelector(selectIsFetchingUser)
</script>
<style>
.user {
background: #fff;
box-shadow: 0px 0px 7px rgba(0,0,0,0.3);
display: grid;
padding: 20px;
justify-content: center;
align-items: center;
border-radius: 8px;
}
.user-image {
width: 100px;
height: 100px;
background-position: center;
background-size: contain;
border-radius: 50%;
margin-bottom: 20px;
justify-self: center;
}
</style>
{#if user && !isFetchingUser}
<div class="user">
<div class="user-image" style={`background-image: url(${user.picture.large});`}></div>
<div>{user.name.title}. {user.name.first} {user.name.last}</div>
</div>
{/if}
.
Wir haben einige Ähnlichkeiten mit Hooks aufgeschrieben, es scheint praktisch, aber es ist nicht bekannt, wie sich dies in Zukunft auswirken wird, wenn wir daraus für ein paar Seiten eine Mini-App erstellen. Die Sagen pflügen auch. Mit Redux Devtools können Sie Redux debuggen und von Aktion zu Aktion springen. Alles funktioniert gut.