Bem-vindos à nossa comunidade!

Junte-se a nós e faça parte hoje mesmo!

Aula: Imutabilidade / CloneDeep

Caique Moraes

Novo Membro
Cadastro
29/3/21
Postagens
8
Curtidas
1
Pontuação
5
Cidade
Jandira
Gostaria de tirar uma dúvida a repeito da imutabilidade.
Se eu tenho um array:
const numbers = [1, 2, 3, 4]
Nunca devo alterar as informações no próprio array, e sim retornar um novo array com os dados transformados.

Mas e se este for um array que contém objtos?
const pessoas: [{name: 'fulano', age: 20}, {name: 'cicrano', age: 22}]
Pois mesmo que eu gere um novo array com ambos os objetos 'fulano' e 'cicrano', por serem tipos não primitivos, tanto no array pessoas como no novo array de pessoas, as referências para fulano e cicrano ainda serão as mesmas na memória. Isto é, se alguém fizer: pessoas[0].name = 'john', vai refletir no novo array também.

Isso aflinge o conceito de imutabilidade?

Se eu tiver um array com objeto dentro, terei que fazer um clone do array e dos objetos também?
 

mourabraz

Membro
Moderador
Cadastro
23/12/20
Postagens
106
Curtidas
155
Pontuação
100
Cidade
Leiria
@Caique Moraes boa tarde...

Gostaria de contribuir com algumas ideias sem te dar uma resposta 100% correta.

A imutabilidade não está "puramente" associada à possibilidade de se mudar algo ou não. Até linguagens "puramente" funcionais possuem a possiblidade de reatribuição (não digo a 100%, pois não conheço todas, mas...)

O que o paradima funcional mais defende com a imutabilidade é a restrição de reatribuição de "variáveis" (entres aspas, pois deixam de variar!!).

O fato que você descreveu é bem interessante e desejável que ocorra. Não a parte da reatribuição da propriedade "name" de um dos objectos presentes no array "pessoas", mas a cópia rasa da estrutura de dados, essa sim, é interessante.

Fazer isto:
JavaScript:
const pessoas: [{name: 'fulano', age: 20}, {name: 'cicrano', age: 22}]

pessoas[0].name = 'john';

de fato fere o princípio da impossibilidade da reatribuição (aka imutabilidade), mas isso não significa que devas começar a realizar deep clones das suas estruturas de dados.

O que você pode fazer é se proibir (em JS é com auto disciplina mesmo :)) de alterar algo.... de reatribuir algo.... hum... ficou confuso né!
Tipo poderíamos resolver o problema assim:

JavaScript:
const pessoas: [{name: 'fulano', age: 20}, {name: 'cicrano', age: 22}]

//cont index = pessoas.findIndex(pessoa => {
// return pessoa.name === 'caso nao soubessemos o index'
//});

pessoas[0]= {
    ...pessoas[0],
    name: 'john'
};

Assim o objeto {name: 'fulano', age: 20} continua inalterado em memória. e o seu array "pessoas" foi atualizado...

Oops então agora eu alterei o array!!!

então:

JavaScript:
const pessoas: [{name: 'fulano', age: 20}, {name: 'cicrano', age: 22}]

const newArrayPessoas= [...pessoas];
newArrayPessoas[0] = {
    ...pessoas[0],
    name: 'john'
};

// ou
const newArrayPessoas = pessoas.map((pessoa, idx)=> {
    if(idx === 0) {
        return {
             ...pessoa,
             name: 'john'
         }
    }

    return pessoa;
})


De fato o javascript é muito permissivo. Não é uma boa linguagem para seguir com ideias muito puras em determinado paradigma. Acho que para os três, procedural, orientado a objectos ou orientado a funções, javascript não consegue "atender" a 100%. Se por um lado isso pode ser ruim, por outro é o que me faz gostar de javascript.

Voltando à questão.... Se alterar a propriedade de um objecto fere ou não o princípio da imutabilidade? Sim fere!
Isso não quer dizer que de tempos em tempo eu não faça uma dessas.... A regra é evitar.

Abraços

PS a resposta é curta e bem incompleta... sorry!
 

Caique Moraes

Novo Membro
Cadastro
29/3/21
Postagens
8
Curtidas
1
Pontuação
5
Cidade
Jandira
@mourabraz meu amigo, excelente! Entendi o raciocínio! Muito mais simples e eficiente que a ideia de fazer um deepClone.
Eu apliquei aqui e saiu como o esperado.
const pessoas = [
{ name: 'caique', id: 0 },
{ name: 'thomas', id: 1 }
]
const newPessoas = [...pessoas]
newPessoas[0] = {
...pessoas[0],
name: 'caique moraes'
}
console.log(pessoas[0] === newPessoas[0])
// false
Então se eu criar uma função pura, que isola esse trecho aqui:
...
const newPessoas = [...pessoas]
newPessoas[0] = {
...pessoas[0],
name: 'caique moraes'
}
return newPessoas

E dentro dela eu crio um clone do array original, clono o objeto alvo, substituo abaixo somente a propriedade atualizada (no caso name), e retorno o novo array, isso segue a princípio da imutabilidade? Mesmo que o novo array tenha sido "violado" dentro da função?
 

mourabraz

Membro
Moderador
Cadastro
23/12/20
Postagens
106
Curtidas
155
Pontuação
100
Cidade
Leiria
@Caique Moraes olá boa tarde!


Para mim segue sim o princípio! Mas se ficares mais confortável sem criar esse array newPessoas e evitar a alteração desse array dentro dessa função também o podes fazer...

Outras linguagens mais voltadas para o pardigma funcional como o OCaml essas questões ficam mais simples, mas o lance do javascript é que você consegue programar com esse paradigma, mas nunca será 100% fiel, em algum momento você vai acabar com coisas desse tipo. Eu aceito tranquilamente, mas sei de pessoas que não aceitariam.

Você com o tempo ganha segurança para se sentir confortável com esse tipo de questão.

Caso você quisesse refazer essa função de outra forma do tipo assim:


JavaScript:
function fn(arr) {
    return arr.map((item, idx) => {
        if(idx === 0) {
            return {
                ...item,
                name: 'caique moraes'
            }
        }

        return item; // ou {...item} mas se item tiver props que sejam do tipo object a sua cópia continua sendo "rasa"
    })
}

desta forma ficaria melhor? Sim... mas no fundo em que que elas diferem?
 

Caique Moraes

Novo Membro
Cadastro
29/3/21
Postagens
8
Curtidas
1
Pontuação
5
Cidade
Jandira
@mourabraz
Muito obrigado mesmo pelas suas exlicações. Elas me ajudaram a criar uma versão mais enxuta da função updateElement do modelo abaixo:

const log = (...args) => console.log(...args)
const initialState = [
{ id: 1, name: 'caique', age: 27 },
{ id: 2, name: 'thomas', age: 20 }
]
const createState = (state = initialState) => {
const managerState = (newState) => {
state = [...newState]
}
const getState = () => {
return [...state]
}
const addElement = (element) => {
managerState([...state, element])
}
const removeElement = (elementToRemove) => {
const newState = getState()
.filter(element =>
JSON.stringify(element) !== JSON.stringify(elementToRemove)
)
managerState(newState)
}
const updateElement = (id, newElement) => {
const newState = getState()
const indexElementToChange = newState.findIndex(
element => element.id === id
)

if (indexElementToChange < 0) {
return false
}
newState[indexElementToChange] = {
...newState[indexElementToChange],
...newElement
}
managerState(newState)
}
return ({
getState,
addElement,
removeElement,
updateElement
})
}
const state = createState()
state.addElement({ id: 3, name: 'isabella', age: 22 })
const elementToRemove = state.getState()[0]
state.removeElement(elementToRemove)
state.updateElement(2, { name: 'thomas moraes' })
log(state.getState())

Eu criei essa função: createState, inspirado nas funções de reducer que passamos para o reducex gerenciar nossos estados.
Meu objetivo é criar um função responsável por gerenciar o estado da minha aplicação, sempre me devolvendo um objeto/array novo toda vez que ele for alterado (adicionado elemento, atualizado elemento ou removido elemento).
Por isso tive essas dúvidas desde o início sobre o cloneDeep. Pensei que todas as vezes que eu fizesse alguma alteração na variável state, eu deveria fazer uma clonagem de todos os elementos.

Segundo as primeiras aulas do curso de Javascript Reativo, o professor fala que na programação funcional pode haver uma mutabilidade, desde que ela seja feita num ambiente fechado e controlado. Então eu imagino minha função createState como esse "ambiente fechado e controlado".
A função managerState é responsável por alterar a variável state que está no closure, como se trata de um tipo não primitivo, eu estou mudando o endereço de memória dela apontando para um novo array de objetos.

Essa atribuição que eu faço no managerState, é válida? Eu não consigo encontrar outro meio de fazer isso, para armazenar o estado.
O que você acha dessa função createState?
 
Last edited:

Caique Moraes

Novo Membro
Cadastro
29/3/21
Postagens
8
Curtidas
1
Pontuação
5
Cidade
Jandira
Na função managerState e na getState, eu fiz a seguinte mudança.
Adicionei o Object.freeze, sei que será uma proteção raza.

const managerState = (newState) => {
state = newState.map(Object.freeze)
}

const getState = () => {
return state.map(Object.freeze)
}



Pois eu sei que se eu fizer isso:
const state = createState()
const p1 = state.getState()[0].name = 'caique moraes'
log(state.getState()[0].name) // caique moraes

ele mudou de caique para caique moraes, pois alterei o objeto original.
 

mourabraz

Membro
Moderador
Cadastro
23/12/20
Postagens
106
Curtidas
155
Pontuação
100
Cidade
Leiria
Oi ainda não consegui ver com cuidado... mas estou copiando o seu código e identando ele para ser mais fácil lê-lo...
Assim que der eu volto aqui para lhe responder melhor!


JavaScript:
const log = (...args) => console.log(...args)

const initialState = [
    { id: 1, name: 'caique', age: 27 },
    { id: 2, name: 'thomas', age: 20 }
]

const createState = (state = initialState) => {
    
    const managerState = (newState) => {
        state = [...newState]
    }
    
    const getState = () => {
        return [...state]
    }
    
    const addElement = (element) => {
        managerState([...state, element])
    }

    const removeElement = (elementToRemove) => {
        const newState = getState()
        .filter(element =>
            JSON.stringify(element) !== JSON.stringify(elementToRemove)
        )

        managerState(newState)
    }
    
    const updateElement = (id, newElement) => {
        const newState = getState()
        const indexElementToChange = newState.findIndex(element => element.id === id)

        if (indexElementToChange < 0) {
            return false
        }
    
        newState[indexElementToChange] = {
            ...newState[indexElementToChange],
            ...newElement
        }
        
        managerState(newState)
    }
    
    return ({
        getState,
        addElement,
        removeElement,
        updateElement
    })
}

const state = createState()

state.addElement({ id: 3, name: 'isabella', age: 22 })

const elementToRemove = state.getState()[0]

state.removeElement(elementToRemove)

state.updateElement(2, { name: 'thomas moraes' })

log(state.getState())
 

mourabraz

Membro
Moderador
Cadastro
23/12/20
Postagens
106
Curtidas
155
Pontuação
100
Cidade
Leiria
@Caique Moraes Olá bom dia, desculpa a demora!

A função managerState é responsável por alterar a variável state que está no closure, como se trata de um tipo não primitivo, eu estou mudando o endereço de memória dela apontando para um novo array de objetos
Concordo 100%. Mas como cada um tem sempre uma opinião, ahahahah, vou dar uma que não muda nada do que você já fez. É que para mim esse "managerState" tem mais cara de "setState" do que de "manager", mas isso é apenas opinião!


Essa atribuição que eu faço no managerState, é válida? Eu não consigo encontrar outro meio de fazer isso, para armazenar o estado.
JavaScript:
const managerState = (newState) => {
    state = [...newState]
}
Concordo 100%, na minha visão é super válida.


O que você acha dessa função createState?
Na minha opinião ficou show! Só que o nome eu mudaria para "managerState", a mim me parece que essa função não só cria o state como gerencia ele, remove, atualiza e tal.

Camarada não sei se respondi a sua questão, eu tive dificuldade em entender como poderia ajudar nesta questão. Se você reparar acabou ficando mais como uma opinião mesmo. Não vi nada que possa estar ferindo algum princípio e tal. E na minha opinião está muito legal.

Caso surja algum "bug" ou alguma outra situação seria interesse realimentar este "post" para irmos trocando ideias sobre a questão.

Um grande abraço.
 

Caique Moraes

Novo Membro
Cadastro
29/3/21
Postagens
8
Curtidas
1
Pontuação
5
Cidade
Jandira
@mourabraz muito obrigado pela sua ajuda e constribuição. Você tem razão, a ela tem mais cara de managerState do que createState, enquanto que a managerState interna tem cara de setState.
Muito obrigado mesmo. O paradigma funcional tem me chamado muita atenção nos últimos meses. Eu encontrei alguns artigos antigo em JS, falando a respeito, que também me ajudaram a enriquecer meu repertório:

Além de claro, do curso da Cod3er do paradgma funcional, para mim, foi um dos melhores cursos que já fiz pois de fato me deu uma nova mentalidade para resolver problemas.
 
Top